From 25dc87a63344e5c3d63daf7a5df3923f8f0e816d Mon Sep 17 00:00:00 2001 From: miles Date: Mon, 31 Oct 2022 16:45:21 +0800 Subject: [PATCH] Add Methods `Chat.mention_{html, markdown, markdown_v2}` (#3308) --- telegram/_chat.py | 105 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_chat.py | 94 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/telegram/_chat.py b/telegram/_chat.py index 39bce15e0..4ac457c45 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -19,6 +19,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram Chat.""" from datetime import datetime +from html import escape from typing import TYPE_CHECKING, ClassVar, List, Optional, Tuple, Union from telegram import constants @@ -30,6 +31,9 @@ from telegram._telegramobject import TelegramObject from telegram._utils import enum from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup +from telegram.helpers import escape_markdown +from telegram.helpers import mention_html as helpers_mention_html +from telegram.helpers import mention_markdown as helpers_mention_markdown if TYPE_CHECKING: from telegram import ( @@ -355,6 +359,107 @@ class Chat(TelegramObject): return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) + def mention_markdown(self, name: str = None) -> str: + """ + Note: + :tg-const:`telegram.constants.ParseMode.MARKDOWN` is a legacy mode, retained by + Telegram for backward compatibility. You should use :meth:`mention_markdown_v2` + instead. + + .. versionadded:: 20.0 + + Args: + name (:obj:`str`): The name used as a link for the chat. Defaults to :attr:`full_name`. + + Returns: + :obj:`str`: The inline mention for the chat as markdown (version 1). + + Raises: + :exc:`TypeError`: If the chat is a private chat and neither the :paramref:`name` + nor the :attr:`first_name` is set, then throw an :exc:`TypeError`. + If the chat is a public chat and neither the :paramref:`name` nor the :attr:`title` + is set, then throw an :exc:`TypeError`. If chat is a private group chat, then + throw an :exc:`TypeError`. + + """ + if self.type == self.PRIVATE: + if name: + return helpers_mention_markdown(self.id, name) + if self.full_name: + return helpers_mention_markdown(self.id, self.full_name) + raise TypeError("Can not create a mention to a private chat without first name") + if self.username: + if name: + return f"[{name}]({self.link})" + if self.title: + return f"[{self.title}]({self.link})" + raise TypeError("Can not create a mention to a public chat without title") + raise TypeError("Can not create a mention to a private group chat") + + def mention_markdown_v2(self, name: str = None) -> str: + """ + .. versionadded:: 20.0 + + Args: + name (:obj:`str`): The name used as a link for the chat. Defaults to :attr:`full_name`. + + Returns: + :obj:`str`: The inline mention for the chat as markdown (version 2). + + Raises: + :exc:`TypeError`: If the chat is a private chat and neither the :paramref:`name` + nor the :attr:`first_name` is set, then throw an :exc:`TypeError`. + If the chat is a public chat and neither the :paramref:`name` nor the :attr:`title` + is set, then throw an :exc:`TypeError`. If chat is a private group chat, then + throw an :exc:`TypeError`. + + """ + if self.type == self.PRIVATE: + if name: + return helpers_mention_markdown(self.id, name, version=2) + if self.full_name: + return helpers_mention_markdown(self.id, self.full_name, version=2) + raise TypeError("Can not create a mention to a private chat without first name") + if self.username: + if name: + return f"[{escape_markdown(name, version=2)}]({self.link})" + if self.title: + return f"[{escape_markdown(self.title, version=2)}]({self.link})" + raise TypeError("Can not create a mention to a public chat without title") + raise TypeError("Can not create a mention to a private group chat") + + def mention_html(self, name: str = None) -> str: + """ + .. versionadded:: 20.0 + + Args: + name (:obj:`str`): The name used as a link for the chat. Defaults to :attr:`full_name`. + + Returns: + :obj:`str`: The inline mention for the chat as HTML. + + Raises: + :exc:`TypeError`: If the chat is a private chat and neither the :paramref:`name` + nor the :attr:`first_name` is set, then throw an :exc:`TypeError`. + If the chat is a public chat and neither the :paramref:`name` nor the :attr:`title` + is set, then throw an :exc:`TypeError`. If chat is a private group chat, then + throw an :exc:`TypeError`. + + """ + if self.type == self.PRIVATE: + if name: + return helpers_mention_html(self.id, name) + if self.full_name: + return helpers_mention_html(self.id, self.full_name) + raise TypeError("Can not create a mention to a private chat without first name") + if self.username: + if name: + return f'{escape(name)}' + if self.title: + return f'{escape(self.title)}' + raise TypeError("Can not create a mention to a public chat without title") + raise TypeError("Can not create a mention to a private group chat") + async def leave( self, *, diff --git a/tests/test_chat.py b/tests/test_chat.py index 1da9f4ae1..ddc9d3cdb 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -21,6 +21,7 @@ import pytest from telegram import Bot, Chat, ChatLocation, ChatPermissions, Location, User from telegram.constants import ChatAction, ChatType +from telegram.helpers import escape_markdown from tests.conftest import check_defaults_handling, check_shortcut_call, check_shortcut_signature @@ -880,6 +881,99 @@ class TestChat: monkeypatch.setattr(chat.get_bot(), "decline_chat_join_request", make_assertion) assert await chat.decline_join_request(user_id=42) + def test_mention_html(self): + with pytest.raises(TypeError, match="Can not create a mention to a private group chat"): + chat = Chat(id=1, type="foo") + chat.mention_html() + + expected = '{}' + chat = Chat( + id=1, type=Chat.PRIVATE, first_name="first\u2022name", last_name="last\u2022name" + ) + assert chat.mention_html("the_name*\u2022") == expected.format(chat.id, "the_name*\u2022") + assert chat.mention_html() == expected.format(chat.id, chat.full_name) + with pytest.raises( + TypeError, match="Can not create a mention to a private chat without first name" + ): + chat = Chat(id=1, type=Chat.PRIVATE, last_name="last\u2022name") + chat.mention_html() + + expected = '{}' + chat = Chat(id=1, type="foo", username="user\u2022name", title="\u2022title") + assert chat.mention_html("the_name*\u2022") == expected.format( + chat.username, "the_name*\u2022" + ) + assert chat.mention_html() == expected.format(chat.username, chat.title) + with pytest.raises( + TypeError, match="Can not create a mention to a public chat without title" + ): + chat = Chat(id=1, type="foo", username="user\u2022name") + chat.mention_html() + + def test_mention_markdown(self): + with pytest.raises(TypeError, match="Can not create a mention to a private group chat"): + chat = Chat(id=1, type="foo") + chat.mention_markdown() + + expected = "[{}](tg://user?id={})" + chat = Chat( + id=1, type=Chat.PRIVATE, first_name="first\u2022name", last_name="last\u2022name" + ) + assert chat.mention_markdown("the_name*\u2022") == expected.format( + "the_name*\u2022", chat.id + ) + assert chat.mention_markdown() == expected.format(chat.full_name, chat.id) + with pytest.raises( + TypeError, match="Can not create a mention to a private chat without first name" + ): + chat = Chat(id=1, type=Chat.PRIVATE, last_name="last\u2022name") + chat.mention_markdown() + + expected = "[{}](https://t.me/{})" + chat = Chat(id=1, type="foo", username="user\u2022name", title="\u2022title") + assert chat.mention_markdown("the_name*\u2022") == expected.format( + "the_name*\u2022", chat.username + ) + assert chat.mention_markdown() == expected.format(chat.title, chat.username) + with pytest.raises( + TypeError, match="Can not create a mention to a public chat without title" + ): + chat = Chat(id=1, type="foo", username="user\u2022name") + chat.mention_markdown() + + def test_mention_markdown_v2(self): + with pytest.raises(TypeError, match="Can not create a mention to a private group chat"): + chat = Chat(id=1, type="foo") + chat.mention_markdown_v2() + + expected = "[{}](tg://user?id={})" + chat = Chat(id=1, type=Chat.PRIVATE, first_name="first{name", last_name="last_name") + assert chat.mention_markdown_v2("the{name>\u2022") == expected.format( + "the\\{name\\>\u2022", chat.id + ) + assert chat.mention_markdown_v2() == expected.format( + escape_markdown(chat.full_name, version=2), chat.id + ) + with pytest.raises( + TypeError, match="Can not create a mention to a private chat without first name" + ): + chat = Chat(id=1, type=Chat.PRIVATE, last_name="last_name") + chat.mention_markdown_v2() + + expected = "[{}](https://t.me/{})" + chat = Chat(id=1, type="foo", username="user{name", title="{title") + assert chat.mention_markdown_v2("the{name>\u2022") == expected.format( + "the\\{name\\>\u2022", chat.username + ) + assert chat.mention_markdown_v2() == expected.format( + escape_markdown(chat.title, version=2), chat.username + ) + with pytest.raises( + TypeError, match="Can not create a mention to a public chat without title" + ): + chat = Chat(id=1, type="foo", username="user\u2022name") + chat.mention_markdown_v2() + def test_equality(self): a = Chat(self.id_, self.title, self.type_) b = Chat(self.id_, self.title, self.type_)