Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/68771.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support Telegram topics by adding an optional thread_id configuration option to the Telegram returner and module.
41 changes: 34 additions & 7 deletions salt/modules/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@
in the pillar. Some sample configs might look like::

telegram.chat_id: '123456789'
telegram.thread_id: '2'
telegram.token: '00000000:xxxxxxxxxxxxxxxxxxxxxxxx'

The ``telegram.thread_id`` is optional and can be used to specify a thread
in a Telegram chat to send the message to. To send messages to the main
chat, simply omit the ``telegram.thread_id``.

"""

import logging
Expand Down Expand Up @@ -67,14 +72,28 @@ def _get_token():
return token


def post_message(message, chat_id=None, token=None):
def _get_thread_id():
"""
Retrieves and return the Telegram's configured thread id

:return: String: the thread id string
"""
thread_id = __salt__["config.get"]("telegram:thread_id") or __salt__["config.get"](
"telegram.thread_id"
)

return thread_id


def post_message(message, chat_id=None, token=None, thread_id=None):
"""
Send a message to a Telegram chat.

:param message: The message to send to the Telegram chat.
:param chat_id: (optional) The Telegram chat id.
:param token: (optional) The Telegram API token.
:return: Boolean if message was sent successfully.
:param message: The message to send to the Telegram chat.
:param chat_id: (optional) The Telegram chat id.
:param token: (optional) The Telegram API token.
:param thread_id: (optional) The Telegram thread id.
:return: Boolean if message was sent successfully.

CLI Example:

Expand All @@ -86,22 +105,28 @@ def post_message(message, chat_id=None, token=None):
if not chat_id:
chat_id = _get_chat_id()

if not thread_id:
thread_id = _get_thread_id()

if not token:
token = _get_token()

if not message:
log.error("message is a required option.")

return _post_message(message=message, chat_id=chat_id, token=token)
return _post_message(
message=message, chat_id=chat_id, token=token, thread_id=thread_id
)


def _post_message(message, chat_id, token):
def _post_message(message, chat_id, token, thread_id=None):
"""
Send a message to a Telegram chat.

:param chat_id: The chat id.
:param message: The message to send to the telegram chat.
:param token: The Telegram API token.
:param thread_id: (optional) The Telegram thread id.
:return: Boolean if message was sent successfully.
"""
url = f"https://api.telegram.org/bot{token}/sendMessage"
Expand All @@ -111,6 +136,8 @@ def _post_message(message, chat_id, token):
parameters["chat_id"] = chat_id
if message:
parameters["text"] = message
if thread_id:
parameters["message_thread_id"] = thread_id

try:
response = requests.post(url, data=parameters, timeout=120)
Expand Down
13 changes: 11 additions & 2 deletions salt/returners/telegram_return.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

telegram.chat_id (required)
telegram.token (required)
telegram.thread_id (optional)

Telegram settings may also be configured as:

Expand All @@ -13,6 +14,11 @@
telegram:
chat_id: 000000000
token: 000000000:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
thread_id: 0000

The ``telegram.thread_id`` is optional and can be used to specify a thread
in a Telegram chat to send the message to. To send messages to the main
chat, simply omit the ``telegram.thread_id``.

To use the Telegram return, append '--return telegram' to the salt command.

Expand Down Expand Up @@ -48,7 +54,7 @@ def _get_options(ret=None):
:return: Dictionary containing the data and options needed to send
them to telegram.
"""
attrs = {"chat_id": "chat_id", "token": "token"}
attrs = {"chat_id": "chat_id", "token": "token", "thread_id": "thread_id"}

_options = salt.returners.get_returner_options(
__virtualname__, ret, attrs, __salt__=__salt__, __opts__=__opts__
Expand All @@ -68,6 +74,7 @@ def returner(ret):

chat_id = _options.get("chat_id")
token = _options.get("token")
thread_id = _options.get("thread_id")

if not chat_id:
log.error("telegram.chat_id not defined in salt config")
Expand All @@ -80,4 +87,6 @@ def returner(ret):
ret.get("id"), ret.get("fun"), ret.get("fun_args"), ret.get("jid"), returns
)

return __salt__["telegram.post_message"](message, chat_id=chat_id, token=token)
return __salt__["telegram.post_message"](
message, chat_id=chat_id, token=token, thread_id=thread_id
)
1 change: 1 addition & 0 deletions tests/pytests/unit/modules/test_telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def configure_loader_modules():
"telegram": {
"chat_id": "123456789",
"token": "000000000:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"thread_id": "2",
}
}
),
Expand Down
47 changes: 38 additions & 9 deletions tests/pytests/unit/returners/test_telegram_return.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,49 @@ def test_returner():
"""
Test to see if the Telegram returner sends a message
"""
ret = {
ret: dict[str, str] = {
"id": "12345",
"fun": "mytest.func",
"fun_args": "myfunc args",
"jid": "54321",
"return": "The room is on fire as shes fixing her hair",
}
options = {"chat_id": "", "token": ""}

with patch(
"salt.returners.telegram_return._get_options",
MagicMock(return_value=options),
), patch.dict(
"salt.returners.telegram_return.__salt__",
{"telegram.post_message": MagicMock(return_value=True)},
options: dict[str, str] = {"chat_id": "", "token": ""}

with (
patch(
"salt.returners.telegram_return._get_options",
MagicMock(return_value=options),
),
patch.dict(
"salt.returners.telegram_return.__salt__",
{"telegram.post_message": MagicMock(return_value=True)},
),
):
assert telegram.returner(ret) is True


def test_returner_topics():
"""
Test to see if the Telegram returner sends a message to specific topic
"""
ret: dict[str, str] = {
"id": "12345",
"fun": "mytest.func",
"fun_args": "myfunc args",
"jid": "54321",
"return": "The room is on fire as shes fixing her hair",
}
options: dict[str, str] = {"chat_id": "", "token": "", "thread_id": ""}

with (
patch(
"salt.returners.telegram_return._get_options",
MagicMock(return_value=options),
),
patch.dict(
"salt.returners.telegram_return.__salt__",
{"telegram.post_message": MagicMock(return_value=True)},
),
):
assert telegram.returner(ret) is True
Loading