diff --git a/telegram/_chat.py b/telegram/_chat.py index fe49dc3593e..1ad7d2d229d 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -39,6 +39,7 @@ ReplyMarkup, TimePeriod, ) +from telegram._utils.usernames import get_link 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 @@ -162,9 +163,7 @@ def link(self) -> Optional[str]: """:obj:`str`: Convenience property. If the chat has a :attr:`~Chat.username`, returns a t.me link of the chat. """ - if self.username: - return f"https://t.me/{self.username}" - return None + return get_link(user=self) def mention_markdown(self, name: Optional[str] = None) -> str: """ diff --git a/telegram/_shared.py b/telegram/_shared.py index 9c0d3684ec2..1334625cb42 100644 --- a/telegram/_shared.py +++ b/telegram/_shared.py @@ -18,11 +18,12 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains two objects used for request chats/users service messages.""" from collections.abc import Sequence -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Union from telegram._files.photosize import PhotoSize from telegram._telegramobject import TelegramObject from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg +from telegram._utils.usernames import get_name, get_full_name, get_link from telegram._utils.types import JSONDict if TYPE_CHECKING: @@ -244,6 +245,27 @@ def __init__( self._freeze() + @property + def name(self) -> Union[str, None]: + """:obj:`str`: Convenience property. If available, returns the user's :attr:`username` + prefixed with "@". If :attr:`username` is not available, returns :attr:`full_name`. + """ + return get_name(user=self, ) + + @property + def full_name(self) -> Union[str, None]: + """:obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if + available) :attr:`last_name`, otherwise None. + """ + return get_full_name(user=self, ) + + @property + def link(self) -> Union[str, None]: + """:obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if + available) :attr:`last_name`, otherwise None. + """ + return get_link(user=self, ) + @classmethod def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SharedUser": """See :meth:`telegram.TelegramObject.de_json`.""" diff --git a/telegram/_user.py b/telegram/_user.py index 640a3573acc..1e04c59c953 100644 --- a/telegram/_user.py +++ b/telegram/_user.py @@ -26,6 +26,7 @@ from telegram._menubutton import MenuButton from telegram._telegramobject import TelegramObject from telegram._utils.defaultvalue import DEFAULT_NONE +from telegram._utils.usernames import get_name, get_full_name, get_link from telegram._utils.types import ( CorrectOptionID, FileInput, @@ -207,27 +208,21 @@ def name(self) -> str: """:obj:`str`: Convenience property. If available, returns the user's :attr:`username` prefixed with "@". If :attr:`username` is not available, returns :attr:`full_name`. """ - if self.username: - return f"@{self.username}" - return self.full_name + return get_name(self, ) @property def full_name(self) -> str: """:obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if available) :attr:`last_name`. """ - if self.last_name: - return f"{self.first_name} {self.last_name}" - return self.first_name + return get_full_name(self, ) @property def link(self) -> Optional[str]: """:obj:`str`: Convenience property. If :attr:`username` is available, returns a t.me link of the user. """ - if self.username: - return f"https://t.me/{self.username}" - return None + return get_link(self, ) async def get_profile_photos( self, diff --git a/telegram/_utils/usernames.py b/telegram/_utils/usernames.py new file mode 100644 index 00000000000..1f4e393c9d7 --- /dev/null +++ b/telegram/_utils/usernames.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2025 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""Shared properties to extract username, first_name, last_name values if filled.""" +from __future__ import annotations +from typing import Protocol, overload + + +class UserLikeOptional(Protocol): + """ + Note: + `User`, `Contact` (and maybe some other) objects always have first_name, + unlike the `Chat` and `Shared`, were they are optional. + The `last_name` is always optional. + """ + last_name: str | None + username: str | None + + +class UserLike(UserLikeOptional): + """ + Note: + `User`, `Contact` (and maybe some other) objects always have first_name, + unlike the `Chat` and `Shared`, were they are optional. + The `last_name` is always optional. + """ + first_name: str + + +class MiniUserLike(UserLikeOptional): + """ + Note: + `User`, `Contact` (and maybe some other) objects always have first_name, + unlike the `Chat` and `Shared`, were they are optional. + The `last_name` is always optional. + """ + first_name: str | None + + +@overload +def get_name(user: UserLike) -> str: + ... + + +@overload +def get_name(user: MiniUserLike) -> str | None: + ... + + +def get_name(user: UserLike | MiniUserLike) -> str | None: + """:obj:`str`: Convenience property. If available, returns the user's :attr:`username` + prefixed with "@". If :attr:`username` is not available, returns :attr:`full_name`. + For the UserLike object str will always be returned as `first_name`always exists. + """ + if user.username: + return f"@{user.username}" + return get_full_name(user=user, ) + + +@overload +def get_full_name(user: UserLike) -> str: + ... + + +@overload +def get_full_name(user: MiniUserLike) -> str | None: + ... + + +def get_full_name(user: UserLike | MiniUserLike) -> str | None: + """:obj:`str`: Convenience property. The user's :attr:`first_name`, followed by (if + available) :attr:`last_name`, otherwise None. + For the UserLike object str will always be returned as `first_name`always exists. + """ + if user.first_name and user.last_name: + return f"{user.first_name} {user.last_name}" + if user.first_name or user.last_name: + return f"{user.first_name or user.last_name}" + return None + + +def get_link(user: UserLike | MiniUserLike) -> str | None: + """:obj:`str`: Convenience property. If :attr:`username` is available, returns a t.me link + of the user. + """ + if user.username: + return f"https://t.me/{user.username}" + return None diff --git a/tests/_utils/test_usernames.py b/tests/_utils/test_usernames.py new file mode 100644 index 00000000000..8f84e465d9a --- /dev/null +++ b/tests/_utils/test_usernames.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2025 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +from __future__ import annotations +import pytest +from typing import TYPE_CHECKING +from telegram import SharedUser +from tests.test_user import user # noqa: F401 noqa: F811 +from telegram._utils.usernames import get_name, get_full_name, get_link + +if TYPE_CHECKING: + from telegram._utils.usernames import UserLike, MiniUserLike + + +@pytest.fixture(scope="class") +def shared_user(): + result = SharedUser( + user_id=1, + first_name="first\u2022name", + last_name="last\u2022name", + username="username", + ) + result._unfreeze() + return result + + +def test_get_name(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811 + assert get_name(user=user) == get_name(user=shared_user) == "@username" + shared_user.username = user.username = None + assert get_name(user=user) == get_name(user=shared_user) == "first\u2022name last\u2022name" + + +def test_full_name_both_exists(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811 + expected = "first\u2022name last\u2022name" + assert get_full_name(user=user) == get_full_name(user=shared_user) == expected + + +def test_full_name_last_name_missed(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811 + user.last_name = shared_user.last_name = None + assert get_full_name(user=user) == get_full_name(user=shared_user) == "first\u2022name" + + +def test_full_name_first_name_missed(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811 + user.first_name = shared_user.first_name = None + assert get_full_name(user=user) == get_full_name(user=shared_user) == "last\u2022name" + + +def test_full_name_both_missed(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811 + user.first_name = user.last_name = shared_user.first_name = shared_user.last_name = None + assert get_full_name(user=user) is get_full_name(user=shared_user) is None + + +def test_link(user: UserLike, shared_user: MiniUserLike, ): # noqa: F811 + assert get_link(user=user, ) == get_link(user=shared_user, ) == f"https://t.me/{user.username}" + user.username = shared_user.username = None + assert get_link(user=user, ) is get_link(user=shared_user, ) is None pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy