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 <nick@koston.org>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
r-xyz 2024-06-05 17:41:40 +02:00 committed by GitHub
parent 862c04a4b6
commit 0487b38ed3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 117 additions and 1 deletions

View file

@ -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)

View file

@ -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,
)

View file

@ -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:

View file

@ -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%]"
}
}
},

View file

@ -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,