From 0487b38ed3c1dadb8ad5f5ebee65f45bdc5e8f13 Mon Sep 17 00:00:00 2001 From: r-xyz <100710244+r-xyz@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:41:40 +0200 Subject: [PATCH] Add support for sending telegram messages to topics (#112715) * Add support for sending telegram messages to topics Based on original PR #104059 by [jgresty](https://github.com/jgresty). Did not manage to merge conflicts, so I remade the changes from scratch, including suggestions from previous PR reviews. Topics were added to telegram groups in November 2022, and to the telegram-bot library in version 20.0. They are a purely additive change that is exposed by a single parameter `message_thread_id`. Not passing this parameter will not change the behaviour from current. This same parameter is used to send messages to threads and messages to topics inside groups. https://telegram.org/blog/topics-in-groups-collectible-usernames/it?setln=en#topics-in-groups Fixes #81888 Fixes #91750 * telegram_bot: add tests for threads feature. * telegram_bot: fixed tests for threads. * telegram_bot: fixed wrong line. * Update test_telegram_bot.py --------- Co-authored-by: J. Nick Koston Co-authored-by: Erik Montnemery --- homeassistant/components/telegram/notify.py | 6 ++++ .../components/telegram_bot/__init__.py | 18 ++++++++++ .../components/telegram_bot/services.yaml | 36 +++++++++++++++++++ .../components/telegram_bot/strings.json | 36 +++++++++++++++++++ .../telegram_bot/test_telegram_bot.py | 22 +++++++++++- 5 files changed, 117 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/telegram/notify.py b/homeassistant/components/telegram/notify.py index df20b98070c..16952868525 100644 --- a/homeassistant/components/telegram/notify.py +++ b/homeassistant/components/telegram/notify.py @@ -18,6 +18,7 @@ from homeassistant.components.telegram_bot import ( ATTR_DISABLE_NOTIF, ATTR_DISABLE_WEB_PREV, ATTR_MESSAGE_TAG, + ATTR_MESSAGE_THREAD_ID, ATTR_PARSER, ) from homeassistant.const import ATTR_LOCATION @@ -91,6 +92,11 @@ class TelegramNotificationService(BaseNotificationService): disable_web_page_preview = data[ATTR_DISABLE_WEB_PREV] service_data.update({ATTR_DISABLE_WEB_PREV: disable_web_page_preview}) + # Set message_thread_id + if data is not None and ATTR_MESSAGE_THREAD_ID in data: + message_thread_id = data[ATTR_MESSAGE_THREAD_ID] + service_data.update({ATTR_MESSAGE_THREAD_ID: message_thread_id}) + # Get keyboard info if data is not None and ATTR_KEYBOARD in data: keys = data.get(ATTR_KEYBOARD) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 06c15da5f70..f37a84a83a6 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -90,6 +90,7 @@ ATTR_ANSWERS = "answers" ATTR_OPEN_PERIOD = "open_period" ATTR_IS_ANONYMOUS = "is_anonymous" ATTR_ALLOWS_MULTIPLE_ANSWERS = "allows_multiple_answers" +ATTR_MESSAGE_THREAD_ID = "message_thread_id" CONF_ALLOWED_CHAT_IDS = "allowed_chat_ids" CONF_PROXY_URL = "proxy_url" @@ -639,6 +640,7 @@ class TelegramNotificationService: ATTR_REPLYMARKUP: None, ATTR_TIMEOUT: None, ATTR_MESSAGE_TAG: None, + ATTR_MESSAGE_THREAD_ID: None, } if data is not None: if ATTR_PARSER in data: @@ -655,6 +657,8 @@ class TelegramNotificationService: params[ATTR_REPLY_TO_MSGID] = data[ATTR_REPLY_TO_MSGID] if ATTR_MESSAGE_TAG in data: params[ATTR_MESSAGE_TAG] = data[ATTR_MESSAGE_TAG] + if ATTR_MESSAGE_THREAD_ID in data: + params[ATTR_MESSAGE_THREAD_ID] = data[ATTR_MESSAGE_THREAD_ID] # Keyboards: if ATTR_KEYBOARD in data: keys = data.get(ATTR_KEYBOARD) @@ -698,6 +702,10 @@ class TelegramNotificationService: } if message_tag is not None: event_data[ATTR_MESSAGE_TAG] = message_tag + if kwargs_msg[ATTR_MESSAGE_THREAD_ID] is not None: + event_data[ATTR_MESSAGE_THREAD_ID] = kwargs_msg[ + ATTR_MESSAGE_THREAD_ID + ] self.hass.bus.async_fire( EVENT_TELEGRAM_SENT, event_data, context=context ) @@ -731,6 +739,7 @@ class TelegramNotificationService: reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) @@ -864,6 +873,7 @@ class TelegramNotificationService: reply_markup=params[ATTR_REPLYMARKUP], read_timeout=params[ATTR_TIMEOUT], parse_mode=params[ATTR_PARSER], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) @@ -878,6 +888,7 @@ class TelegramNotificationService: reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) @@ -894,6 +905,7 @@ class TelegramNotificationService: reply_markup=params[ATTR_REPLYMARKUP], read_timeout=params[ATTR_TIMEOUT], parse_mode=params[ATTR_PARSER], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) elif file_type == SERVICE_SEND_DOCUMENT: @@ -909,6 +921,7 @@ class TelegramNotificationService: reply_markup=params[ATTR_REPLYMARKUP], read_timeout=params[ATTR_TIMEOUT], parse_mode=params[ATTR_PARSER], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) elif file_type == SERVICE_SEND_VOICE: @@ -923,6 +936,7 @@ class TelegramNotificationService: reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) elif file_type == SERVICE_SEND_ANIMATION: @@ -938,6 +952,7 @@ class TelegramNotificationService: reply_markup=params[ATTR_REPLYMARKUP], read_timeout=params[ATTR_TIMEOUT], parse_mode=params[ATTR_PARSER], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) @@ -961,6 +976,7 @@ class TelegramNotificationService: reply_to_message_id=params[ATTR_REPLY_TO_MSGID], reply_markup=params[ATTR_REPLYMARKUP], read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) else: @@ -987,6 +1003,7 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) @@ -1018,6 +1035,7 @@ class TelegramNotificationService: disable_notification=params[ATTR_DISABLE_NOTIF], reply_to_message_id=params[ATTR_REPLY_TO_MSGID], read_timeout=params[ATTR_TIMEOUT], + message_thread_id=params[ATTR_MESSAGE_THREAD_ID], context=context, ) diff --git a/homeassistant/components/telegram_bot/services.yaml b/homeassistant/components/telegram_bot/services.yaml index d2195c1d6ce..a09f4d8f79b 100644 --- a/homeassistant/components/telegram_bot/services.yaml +++ b/homeassistant/components/telegram_bot/services.yaml @@ -54,6 +54,10 @@ send_message: selector: number: mode: box + message_thread_id: + selector: + number: + mode: box send_photo: fields: @@ -126,6 +130,10 @@ send_photo: selector: number: mode: box + message_thread_id: + selector: + number: + mode: box send_sticker: fields: @@ -190,6 +198,10 @@ send_sticker: selector: number: mode: box + message_thread_id: + selector: + number: + mode: box send_animation: fields: @@ -262,6 +274,10 @@ send_animation: selector: number: mode: box + message_thread_id: + selector: + number: + mode: box send_video: fields: @@ -334,6 +350,10 @@ send_video: selector: number: mode: box + message_thread_id: + selector: + number: + mode: box send_voice: fields: @@ -398,6 +418,10 @@ send_voice: selector: number: mode: box + message_thread_id: + selector: + number: + mode: box send_document: fields: @@ -470,6 +494,10 @@ send_document: selector: number: mode: box + message_thread_id: + selector: + number: + mode: box send_location: fields: @@ -520,6 +548,10 @@ send_location: selector: number: mode: box + message_thread_id: + selector: + number: + mode: box send_poll: fields: @@ -564,6 +596,10 @@ send_poll: selector: number: mode: box + message_thread_id: + selector: + number: + mode: box edit_message: fields: diff --git a/homeassistant/components/telegram_bot/strings.json b/homeassistant/components/telegram_bot/strings.json index aad42081274..1a02543d4ab 100644 --- a/homeassistant/components/telegram_bot/strings.json +++ b/homeassistant/components/telegram_bot/strings.json @@ -47,6 +47,10 @@ "reply_to_message_id": { "name": "Reply to message id", "description": "Mark the message as a reply to a previous message." + }, + "message_thread_id": { + "name": "Message thread id", + "description": "Unique identifier for the target message thread (topic) of the forum; for forum supergroups only." } } }, @@ -113,6 +117,10 @@ "reply_to_message_id": { "name": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::name%]", "description": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::description%]" + }, + "message_thread_id": { + "name": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::name%]", + "description": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::description%]" } } }, @@ -175,6 +183,10 @@ "reply_to_message_id": { "name": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::name%]", "description": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::description%]" + }, + "message_thread_id": { + "name": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::name%]", + "description": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::description%]" } } }, @@ -241,6 +253,10 @@ "reply_to_message_id": { "name": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::name%]", "description": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::description%]" + }, + "message_thread_id": { + "name": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::name%]", + "description": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::description%]" } } }, @@ -307,6 +323,10 @@ "reply_to_message_id": { "name": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::name%]", "description": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::description%]" + }, + "message_thread_id": { + "name": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::name%]", + "description": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::description%]" } } }, @@ -369,6 +389,10 @@ "reply_to_message_id": { "name": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::name%]", "description": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::description%]" + }, + "message_thread_id": { + "name": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::name%]", + "description": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::description%]" } } }, @@ -435,6 +459,10 @@ "reply_to_message_id": { "name": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::name%]", "description": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::description%]" + }, + "message_thread_id": { + "name": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::name%]", + "description": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::description%]" } } }, @@ -477,6 +505,10 @@ "reply_to_message_id": { "name": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::name%]", "description": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::description%]" + }, + "message_thread_id": { + "name": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::name%]", + "description": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::description%]" } } }, @@ -523,6 +555,10 @@ "reply_to_message_id": { "name": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::name%]", "description": "[%key:component::telegram_bot::services::send_message::fields::reply_to_message_id::description%]" + }, + "message_thread_id": { + "name": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::name%]", + "description": "[%key:component::telegram_bot::services::send_message::fields::message_thread_id::description%]" } } }, diff --git a/tests/components/telegram_bot/test_telegram_bot.py b/tests/components/telegram_bot/test_telegram_bot.py index b748b58ad1a..aad758827ca 100644 --- a/tests/components/telegram_bot/test_telegram_bot.py +++ b/tests/components/telegram_bot/test_telegram_bot.py @@ -6,6 +6,7 @@ from telegram import Update from homeassistant.components.telegram_bot import ( ATTR_MESSAGE, + ATTR_MESSAGE_THREAD_ID, DOMAIN, SERVICE_SEND_MESSAGE, ) @@ -35,7 +36,7 @@ async def test_send_message(hass: HomeAssistant, webhook_platform) -> None: await hass.services.async_call( DOMAIN, SERVICE_SEND_MESSAGE, - {ATTR_MESSAGE: "test_message"}, + {ATTR_MESSAGE: "test_message", ATTR_MESSAGE_THREAD_ID: "123"}, blocking=True, context=context, ) @@ -45,6 +46,25 @@ async def test_send_message(hass: HomeAssistant, webhook_platform) -> None: assert events[0].context == context +async def test_send_message_thread(hass: HomeAssistant, webhook_platform) -> None: + """Test the send_message service for threads.""" + context = Context() + events = async_capture_events(hass, "telegram_sent") + + await hass.services.async_call( + DOMAIN, + SERVICE_SEND_MESSAGE, + {ATTR_MESSAGE: "test_message", ATTR_MESSAGE_THREAD_ID: "123"}, + blocking=True, + context=context, + ) + await hass.async_block_till_done() + + assert len(events) == 1 + assert events[0].context == context + assert events[0].data[ATTR_MESSAGE_THREAD_ID] == "123" + + async def test_webhook_endpoint_generates_telegram_text_event( hass: HomeAssistant, webhook_platform,