Skip to content
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Oleg Shlyazhko <https://github.com/ollmer>`_
- `Oleg Sushchenko <https://github.com/feuillemorte>`_
- `Or Bin <https://github.com/OrBin>`_
- `OuYoung <https://github.com/ouyooung>`_
- `overquota <https://github.com/overquota>`_
- `Pablo Martinez <https://github.com/elpekenin>`_
- `Paradox <https://github.com/paradox70>`_
Expand Down
5 changes: 5 additions & 0 deletions changes/unreleased/5133.4HWsCMNxMeNeMnEkJMzKRi.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
other = "Updates to the icon_custom_emoji_id and style fields"
[[pull_requests]]
uid = "5133"
author_uids = ["ouyooung"]
closes_threads = []
51 changes: 50 additions & 1 deletion src/telegram/_inline/inlinekeyboardbutton.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class InlineKeyboardButton(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text`, :attr:`url`, :attr:`login_url`, :attr:`callback_data`,
:attr:`switch_inline_query`, :attr:`switch_inline_query_current_chat`, :attr:`callback_game`,
:attr:`web_app` and :attr:`pay` are equal.
:attr:`web_app`, :attr:`pay`, :attr:`style` and :attr:`icon_custom_emoji_id` are equal.

Note:
* Exactly one of the optional fields must be used to specify type of the button.
Expand All @@ -58,6 +58,9 @@ class InlineKeyboardButton(TelegramObject):
This will only work in Telegram versions released after December 7, 2021.
Older clients will display *unsupported message*.

* :attr:`style` option will only work in Telegram versions released after 6.5.1.
Older clients will display buttons without styling.

Warning:
* If your bot allows your arbitrary callback data, buttons whose callback data is a
non-hashable object will become unhashable. Trying to evaluate ``hash(button)`` will
Expand All @@ -77,6 +80,10 @@ class InlineKeyboardButton(TelegramObject):
:attr:`web_app` is considered as well when comparing objects of this type in terms of
equality.

.. versionchanged:: NEXT.VERSION
:attr:`style` and :attr:`icon_custom_emoji_id` are considered as well when
comparing objects of this type in terms of equality.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to add custom_emoji_id here

Args:
text (:obj:`str`): Label text on the button.
url (:obj:`str`, optional): HTTP or tg:// url to be opened when the button is pressed.
Expand Down Expand Up @@ -141,6 +148,23 @@ class InlineKeyboardButton(TelegramObject):
Note:
This type of button **must** always be the first button in the first row and can
only be used in invoice messages.
style (:obj:`str`, optional): Style of the button. Determines the visual appearance
of the button in supported Telegram clients. Only three values are supported:
:tg-const:`telegram.KeyboardButtonStyle.PRIMARY` (blue),
:tg-const:`telegram.KeyboardButtonStyle.SUCCESS` (green), and
:tg-const:`telegram.KeyboardButtonStyle.DANGER` (red).
Color name aliases :tg-const:`telegram.KeyboardButtonStyle.BLUE`,
:tg-const:`telegram.KeyboardButtonStyle.GREEN`, and
:tg-const:`telegram.KeyboardButtonStyle.RED` are also available.

.. versionadded:: NEXT.VERSION
icon_custom_emoji_id (:obj:`str`, optional): Unique identifier of the
custom emoji shown before the text of the button. Can only be used by bots that
purchased additional usernames on Fragment or in the messages directly sent by the
bot to private, group and supergroup chats if the owner of the bot has a Telegram
Premium subscription.

.. versionadded:: NEXT.VERSION
switch_inline_query_chosen_chat (:class:`telegram.SwitchInlineQueryChosenChat`, optional):
If set, pressing the button will prompt the user to select one of their chats of the
specified type, open that chat and insert the bot's username and the specified inline
Expand Down Expand Up @@ -202,6 +226,23 @@ class InlineKeyboardButton(TelegramObject):
copies the specified text to the clipboard.

.. versionadded:: 21.7
style (:obj:`str`): Optional. Style of the button. Determines the visual appearance
of the button in supported Telegram clients. Only three values are supported:
:tg-const:`telegram.KeyboardButtonStyle.PRIMARY` (blue),
:tg-const:`telegram.KeyboardButtonStyle.SUCCESS` (green), and
:tg-const:`telegram.KeyboardButtonStyle.DANGER` (red).
Color name aliases :tg-const:`telegram.KeyboardButtonStyle.BLUE`,
:tg-const:`telegram.KeyboardButtonStyle.GREEN`, and
:tg-const:`telegram.KeyboardButtonStyle.RED` are also available.

.. versionadded:: NEXT.VERSION
icon_custom_emoji_id (:obj:`str`): Optional. Unique identifier of the
custom emoji shown before the text of the button. Can only be used by bots that
purchased additional usernames on Fragment or in the messages directly sent by the
bot to private, group and supergroup chats if the owner of the bot has a Telegram
Premium subscription.

.. versionadded:: NEXT.VERSION
callback_game (:class:`telegram.CallbackGame`): Optional. Description of the game that will
be launched when the user presses the button.

Expand Down Expand Up @@ -235,8 +276,10 @@ class InlineKeyboardButton(TelegramObject):
"callback_data",
"callback_game",
"copy_text",
"icon_custom_emoji_id",
"login_url",
"pay",
"style",
"switch_inline_query",
"switch_inline_query_chosen_chat",
"switch_inline_query_current_chat",
Expand All @@ -258,6 +301,8 @@ def __init__(
web_app: WebAppInfo | None = None,
switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat | None = None,
copy_text: CopyTextButton | None = None,
style: str | None = None,
icon_custom_emoji_id: str | None = None,
*,
api_kwargs: JSONDict | None = None,
):
Expand All @@ -278,6 +323,8 @@ def __init__(
switch_inline_query_chosen_chat
)
self.copy_text: CopyTextButton | None = copy_text
self.style: str | None = style
self.icon_custom_emoji_id: str | None = icon_custom_emoji_id
self._id_attrs = ()
self._set_id_attrs()

Expand All @@ -294,6 +341,8 @@ def _set_id_attrs(self) -> None:
self.switch_inline_query_current_chat,
self.callback_game,
self.pay,
self.style,
self.icon_custom_emoji_id,
)

@classmethod
Expand Down
53 changes: 51 additions & 2 deletions src/telegram/_keyboardbutton.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class KeyboardButton(TelegramObject):

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text`, :attr:`request_contact`, :attr:`request_location`,
:attr:`request_poll`, :attr:`web_app`, :attr:`request_users` and :attr:`request_chat` are
equal.
:attr:`request_poll`, :attr:`web_app`, :attr:`request_users`, :attr:`request_chat`,
:attr:`style` and :attr:`icon_custom_emoji_id` are equal.

Note:
* Optional fields are mutually exclusive.
Expand All @@ -53,6 +53,8 @@ class KeyboardButton(TelegramObject):
* :attr:`request_users` and :attr:`request_chat` options will only work in Telegram
versions released after 3 February, 2023. Older clients will display unsupported
message.
* :attr:`style` option will only work in Telegram versions released after 6.5.1.
Older clients will display buttons without styling.

.. versionchanged:: 21.0
Removed deprecated argument and attribute ``request_user``.
Expand All @@ -63,6 +65,10 @@ class KeyboardButton(TelegramObject):
:attr:`request_users` and :attr:`request_chat` are considered as well when
comparing objects of this type in terms of equality.

.. versionchanged:: NEXT.VERSION
:attr:`style` and :attr:`icon_custom_emoji_id` are considered as well when
comparing objects of this type in terms of equality.

Args:
text (:obj:`str`): Text of the button. If none of the optional fields are used, it will be
sent to the bot as a message when the button is pressed.
Expand Down Expand Up @@ -92,6 +98,24 @@ class KeyboardButton(TelegramObject):
Available in private chats only.

.. versionadded:: 20.1
style (:obj:`str`, optional): Style of the button. Determines the visual appearance
of the button in supported Telegram clients. Only three values are supported:
:tg-const:`telegram.KeyboardButtonStyle.PRIMARY` (blue),
:tg-const:`telegram.KeyboardButtonStyle.SUCCESS` (green), and
:tg-const:`telegram.KeyboardButtonStyle.DANGER` (red).
Color name aliases :tg-const:`telegram.KeyboardButtonStyle.BLUE`,
:tg-const:`telegram.KeyboardButtonStyle.GREEN`, and
:tg-const:`telegram.KeyboardButtonStyle.RED` are also available.

.. versionadded:: NEXT.VERSION
icon_custom_emoji_id (:obj:`str`, optional): Unique identifier of the
custom emoji shown before the text of the button. Can only be used by bots that
purchased additional usernames on Fragment or in the messages directly sent by the
bot to private, group and supergroup chats if the owner of the bot has a Telegram
Premium subscription.

.. versionadded:: NEXT.VERSION

Attributes:
text (:obj:`str`): Text of the button. If none of the optional fields are used, it will be
sent to the bot as a message when the button is pressed.
Expand Down Expand Up @@ -120,14 +144,33 @@ class KeyboardButton(TelegramObject):
Available in private chats only.

.. versionadded:: 20.1
style (:obj:`str`): Optional. Style of the button. Determines the visual appearance
of the button in supported Telegram clients. Only three values are supported:
:tg-const:`telegram.KeyboardButtonStyle.PRIMARY` (blue),
:tg-const:`telegram.KeyboardButtonStyle.SUCCESS` (green), and
:tg-const:`telegram.KeyboardButtonStyle.DANGER` (red).
Color name aliases :tg-const:`telegram.KeyboardButtonStyle.BLUE`,
:tg-const:`telegram.KeyboardButtonStyle.GREEN`, and
:tg-const:`telegram.KeyboardButtonStyle.RED` are also available.

.. versionadded:: NEXT.VERSION
icon_custom_emoji_id (:obj:`str`): Optional. Unique identifier of the
custom emoji shown before the text of the button. Can only be used by bots that
purchased additional usernames on Fragment or in the messages directly sent by the
bot to private, group and supergroup chats if the owner of the bot has a Telegram
Premium subscription.

.. versionadded:: NEXT.VERSION
"""

__slots__ = (
"icon_custom_emoji_id",
"request_chat",
"request_contact",
"request_location",
"request_poll",
"request_users",
"style",
"text",
"web_app",
)
Expand All @@ -141,6 +184,8 @@ def __init__(
web_app: WebAppInfo | None = None,
request_chat: KeyboardButtonRequestChat | None = None,
request_users: KeyboardButtonRequestUsers | None = None,
style: str | None = None,
icon_custom_emoji_id: str | None = None,
*,
api_kwargs: JSONDict | None = None,
):
Expand All @@ -155,6 +200,8 @@ def __init__(
self.web_app: WebAppInfo | None = web_app
self.request_users: KeyboardButtonRequestUsers | None = request_users
self.request_chat: KeyboardButtonRequestChat | None = request_chat
self.style: str | None = style
self.icon_custom_emoji_id: str | None = icon_custom_emoji_id

self._id_attrs = (
self.text,
Expand All @@ -164,6 +211,8 @@ def __init__(
self.web_app,
self.request_users,
self.request_chat,
self.style,
self.icon_custom_emoji_id,
)

self._freeze()
Expand Down
48 changes: 48 additions & 0 deletions src/telegram/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"InputStoryContentType",
"InvoiceLimit",
"KeyboardButtonRequestUsersLimit",
"KeyboardButtonStyle",
"LocationLimit",
"MaskPosition",
"MediaGroupLimit",
Expand Down Expand Up @@ -1402,6 +1403,53 @@ class InlineKeyboardButtonLimit(IntEnum):
"""


class KeyboardButtonStyle(StringEnum):
"""This enum contains the available button styles for
:class:`telegram.InlineKeyboardButton` and :class:`telegram.KeyboardButton`.
The enum members of this enumeration are instances of :class:`str` and can be treated as such.

.. versionadded:: NEXT.VERSION
"""

__slots__ = ()

PRIMARY = "primary"
""":obj:`str`: Primary button style (usually blue) for the
:paramref:`~telegram.InlineKeyboardButton.style` and
:paramref:`~telegram.KeyboardButton.style` parameters.
"""

SUCCESS = "success"
""":obj:`str`: Success button style (usually green) for the
:paramref:`~telegram.InlineKeyboardButton.style` and
:paramref:`~telegram.KeyboardButton.style` parameters.
"""

DANGER = "danger"
""":obj:`str`: Danger/destructive button style (usually red) for the
:paramref:`~telegram.InlineKeyboardButton.style` and
:paramref:`~telegram.KeyboardButton.style` parameters.
"""

BLUE = "primary"
""":obj:`str`: Alias for :attr:`PRIMARY`. Blue button style for the
:paramref:`~telegram.InlineKeyboardButton.style` and
:paramref:`~telegram.KeyboardButton.style` parameters.
"""

GREEN = "success"
""":obj:`str`: Alias for :attr:`SUCCESS`. Green button style for the
:paramref:`~telegram.InlineKeyboardButton.style` and
:paramref:`~telegram.KeyboardButton.style` parameters.
"""

RED = "danger"
""":obj:`str`: Alias for :attr:`DANGER`. Red button style for the
:paramref:`~telegram.InlineKeyboardButton.style` and
:paramref:`~telegram.KeyboardButton.style` parameters.
"""


class InlineKeyboardMarkupLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.InlineKeyboardMarkup`/
:meth:`telegram.Bot.send_message` & friends. The enum
Expand Down
23 changes: 23 additions & 0 deletions tests/_inline/test_inlinekeyboardbutton.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def inline_keyboard_button():
InlineKeyboardButtonTestBase.switch_inline_query_chosen_chat
),
copy_text=InlineKeyboardButtonTestBase.copy_text,
style=InlineKeyboardButtonTestBase.style,
icon_custom_emoji_id=InlineKeyboardButtonTestBase.icon_custom_emoji_id,
)


Expand All @@ -63,6 +65,8 @@ class InlineKeyboardButtonTestBase:
web_app = WebAppInfo(url="https://example.com")
switch_inline_query_chosen_chat = SwitchInlineQueryChosenChat("a_bot", True, False, True, True)
copy_text = CopyTextButton("python-telegram-bot")
style = "danger"
icon_custom_emoji_id = "5237829955978547322"


class TestInlineKeyboardButtonWithoutRequest(InlineKeyboardButtonTestBase):
Expand Down Expand Up @@ -90,6 +94,8 @@ def test_expected_values(self, inline_keyboard_button):
== self.switch_inline_query_chosen_chat
)
assert inline_keyboard_button.copy_text == self.copy_text
assert inline_keyboard_button.style == self.style
assert inline_keyboard_button.icon_custom_emoji_id == self.icon_custom_emoji_id

def test_to_dict(self, inline_keyboard_button):
inline_keyboard_button_dict = inline_keyboard_button.to_dict()
Expand Down Expand Up @@ -122,6 +128,11 @@ def test_to_dict(self, inline_keyboard_button):
assert (
inline_keyboard_button_dict["copy_text"] == inline_keyboard_button.copy_text.to_dict()
)
assert inline_keyboard_button_dict["style"] == inline_keyboard_button.style
assert (
inline_keyboard_button_dict["icon_custom_emoji_id"]
== inline_keyboard_button.icon_custom_emoji_id
)

def test_de_json(self, offline_bot):
json_dict = {
Expand All @@ -136,6 +147,8 @@ def test_de_json(self, offline_bot):
"pay": self.pay,
"switch_inline_query_chosen_chat": self.switch_inline_query_chosen_chat.to_dict(),
"copy_text": self.copy_text.to_dict(),
"style": self.style,
"icon_custom_emoji_id": self.icon_custom_emoji_id,
}

inline_keyboard_button = InlineKeyboardButton.de_json(json_dict, None)
Expand All @@ -158,6 +171,8 @@ def test_de_json(self, offline_bot):
== self.switch_inline_query_chosen_chat
)
assert inline_keyboard_button.copy_text == self.copy_text
assert inline_keyboard_button.style == self.style
assert inline_keyboard_button.icon_custom_emoji_id == self.icon_custom_emoji_id

def test_equality(self):
a = InlineKeyboardButton("text", callback_data="data")
Expand All @@ -167,6 +182,8 @@ def test_equality(self):
e = InlineKeyboardButton("text", url="http://google.com")
f = InlineKeyboardButton("text", web_app=WebAppInfo(url="https://ptb.org"))
g = LoginUrl("http://google.com")
h = InlineKeyboardButton("test", style="primary")
i = InlineKeyboardButton("test", icon_custom_emoji_id="123")

assert a == b
assert hash(a) == hash(b)
Expand All @@ -186,6 +203,12 @@ def test_equality(self):
assert a != g
assert hash(a) != hash(g)

assert a != h
assert hash(a) != hash(h)

assert h != i
assert hash(h) != hash(i)

@pytest.mark.parametrize("callback_data", ["foo", 1, ("da", "ta"), object()])
def test_update_callback_data(self, callback_data):
button = InlineKeyboardButton(text="test", callback_data="data")
Expand Down
Loading
Loading