Full Support for Bot API 9.3 (#5078)

Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
This commit is contained in:
Abdelrahman Elkheir
2026-01-24 16:20:28 +03:00
committed by GitHub
parent 327f469cb4
commit 432a67efdd
41 changed files with 2570 additions and 134 deletions
+2 -2
View File
@@ -13,7 +13,7 @@ dummy change for chango debug
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-9.2-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-9.3-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API version
@@ -83,7 +83,7 @@ After installing_ the library, be sure to check out the section on `working with
Telegram API support
~~~~~~~~~~~~~~~~~~~~
All types and methods of the Telegram Bot API **9.2** are natively supported by this library.
All types and methods of the Telegram Bot API **9.3** are natively supported by this library.
In addition, Bot API functionality not yet natively included can still be used as described `in our wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Bot-API-Forward-Compatibility>`_.
Notable Features
@@ -0,0 +1,28 @@
features = """
Full Support for Bot API 9.3
.. warning::
- Bot API 9.3 deprecates the field ``last_resale_star_count`` of ``UniqueGiftInfo`` in favor of the new fields ``last_resale_currency`` and ``last_resale_amount``. The field ``last_resale_star_count`` is still present in PTB for backward compatibility, but it will be removed in future releases. Please update your code accordingly.
- Bot API 9.3 deprecates the argument ``exclude_limited`` of ``Bot.get_business_account_gifts`` in favor of the new arguments ``exclude_limited_upgradable`` and ``exclude_limited_non_upgradable``. The argument ``exclude_limited`` is still present in PTB for backward compatibility, but it will be removed in future releases. Please update your code accordingly.
- Bot API 9.3 introduces a now required argument ``gift_id`` to ``UniqueGift``. For backward compatibility, the argument is currently still marked as optional in the signature and it's presence is enforced through a runtime check. In future versions, this argument will be made required in the signature as well.
Please make sure to update your code accordingly to avoid potential issues in the future. We recommend using keyword arguments to ensure compatibility with future updates.
"""
pull_requests = [
{ uid = "5086", author_uids = ["Bibo-Joshi"] },
{ uid = "5078", author_uid = "aelkheir", closes_threads = ["5077"] },
{ uid = "5079", author_uid = "aelkheir" },
{ uid = "5084", author_uids = ["Bibo-Joshi"] },
{ uid = "5085", author_uids = ["Bibo-Joshi"] },
{ uid = "5087", author_uids = ["Bibo-Joshi"] },
{ uid = "5091", author_uids = ["Bibo-Joshi"] },
{ uid = "5090", author_uids = ["Bibo-Joshi"] },
{ uid = "5089", author_uids = ["Bibo-Joshi"] },
{ uid = "5092", author_uids = ["Bibo-Joshi"] },
{ uid = "5095", author_uids = ["Bibo-Joshi"] },
{ uid = "5094", author_uids = ["Bibo-Joshi"] },
{ uid = "5106", author_uid = ["aelkheir"] },
]
+8
View File
@@ -35,6 +35,8 @@
- Used for sending media grouped together
* - :meth:`~telegram.Bot.send_message`
- Used for sending text messages
* - :meth:`~telegram.Bot.send_message_draft`
- Used for streaming partial text messages
* - :meth:`~telegram.Bot.send_paid_media`
- Used for sending paid media to channels
* - :meth:`~telegram.Bot.send_photo`
@@ -443,6 +445,8 @@
- Used for setting the business accounts profile photo
* - :meth:`~telegram.Bot.post_story`
- Used for posting a story on behalf of business account.
* - :meth:`~telegram.Bot.repost_story`
- Used for reposting an existing story on behalf of business account.
* - :meth:`~telegram.Bot.edit_story`
- Used for editing business stories posted by the bot.
* - :meth:`~telegram.Bot.convert_gift_to_stars`
@@ -481,8 +485,12 @@
- Used for getting basic info about a file
* - :meth:`~telegram.Bot.get_available_gifts`
- Used for getting information about gifts available for sending
* - :meth:`~telegram.Bot.get_chat_gifts`
- Used for getting information about gifts owned and hosted by a chat
* - :meth:`~telegram.Bot.get_me`
- Used for getting basic information about the bot
* - :meth:`~telegram.Bot.get_user_gifts`
- Used for getting information about gifts owned and hosted by a user
* - :meth:`~telegram.Bot.save_prepared_inline_message`
- Used for storing a message to be sent by a user of a Mini App
+3
View File
@@ -83,6 +83,7 @@ Available Types
telegram.forumtopicreopened
telegram.generalforumtopichidden
telegram.generalforumtopicunhidden
telegram.giftbackground
telegram.giftinfo
telegram.giveaway
telegram.giveawaycompleted
@@ -181,6 +182,7 @@ Available Types
telegram.telegramobject
telegram.textquote
telegram.uniquegift
telegram.uniquegiftcolors
telegram.uniquegiftbackdrop
telegram.uniquegiftbackdropcolors
telegram.uniquegiftinfo
@@ -190,6 +192,7 @@ Available Types
telegram.user
telegram.userchatboosts
telegram.userprofilephotos
telegram.userrating
telegram.usersshared
telegram.venue
telegram.video
+7
View File
@@ -0,0 +1,7 @@
GiftBackground
==============
.. autoclass:: telegram.GiftBackground
:members:
:show-inheritance:
@@ -0,0 +1,7 @@
UniqueGiftColors
================
.. autoclass:: telegram.UniqueGiftColors
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
UserRating
==========
.. autoclass:: telegram.UserRating
:members:
:show-inheritance:
+1 -1
View File
@@ -30,7 +30,7 @@
.. |message_thread_id| replace:: Unique identifier for the target message thread of the forum topic.
.. |message_thread_id_arg| replace:: Unique identifier for the target message thread (topic) of the forum; for forum supergroups only.
.. |message_thread_id_arg| replace:: Unique identifier for the target message thread (topic) of a forum; for forum supergroups and private chats of bots with forum topic mode enabled only.
.. |parse_mode| replace:: Mode for parsing entities. See :class:`telegram.constants.ParseMode` and `formatting options <https://core.telegram.org/bots/api#formatting-options>`__ for more details.
+6 -1
View File
@@ -111,6 +111,7 @@ __all__ = (
"GeneralForumTopicHidden",
"GeneralForumTopicUnhidden",
"Gift",
"GiftBackground",
"GiftInfo",
"Gifts",
"Giveaway",
@@ -287,6 +288,7 @@ __all__ = (
"UniqueGift",
"UniqueGiftBackdrop",
"UniqueGiftBackdropColors",
"UniqueGiftColors",
"UniqueGiftInfo",
"UniqueGiftModel",
"UniqueGiftSymbol",
@@ -294,6 +296,7 @@ __all__ = (
"User",
"UserChatBoosts",
"UserProfilePhotos",
"UserRating",
"UsersShared",
"Venue",
"Video",
@@ -453,7 +456,7 @@ from ._forumtopic import (
from ._games.callbackgame import CallbackGame
from ._games.game import Game
from ._games.gamehighscore import GameHighScore
from ._gifts import AcceptedGiftTypes, Gift, GiftInfo, Gifts
from ._gifts import AcceptedGiftTypes, Gift, GiftBackground, GiftInfo, Gifts
from ._giveaway import Giveaway, GiveawayCompleted, GiveawayCreated, GiveawayWinners
from ._inline.inlinekeyboardbutton import InlineKeyboardButton
from ._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
@@ -597,6 +600,7 @@ from ._uniquegift import (
UniqueGift,
UniqueGiftBackdrop,
UniqueGiftBackdropColors,
UniqueGiftColors,
UniqueGiftInfo,
UniqueGiftModel,
UniqueGiftSymbol,
@@ -604,6 +608,7 @@ from ._uniquegift import (
from ._update import Update
from ._user import User
from ._userprofilephotos import UserProfilePhotos
from ._userrating import UserRating
from ._videochat import (
VideoChatEnded,
VideoChatParticipantsInvited,
+359 -10
View File
@@ -106,13 +106,14 @@ from telegram._utils.types import (
TimePeriod,
)
from telegram._utils.warnings import warn
from telegram._utils.warnings_transition import build_deprecation_warning_message
from telegram._webhookinfo import WebhookInfo
from telegram.constants import InlineQueryLimit, ReactionEmoji
from telegram.error import EndPointNotFound, InvalidToken
from telegram.request import BaseRequest, RequestData
from telegram.request._httpxrequest import HTTPXRequest
from telegram.request._requestparameter import RequestParameter
from telegram.warnings import PTBUserWarning
from telegram.warnings import PTBDeprecationWarning, PTBUserWarning
if TYPE_CHECKING:
from telegram import (
@@ -1200,6 +1201,69 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
async def send_message_draft(
self,
chat_id: int,
draft_id: int,
text: str,
message_thread_id: int | None = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
entities: Sequence["MessageEntity"] | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> bool:
"""Use this method to stream a partial message to a user while the message is being
generated; supported only for bots with forum topic mode enabled.
.. versionadded:: NEXT.VERSION
Args:
chat_id (:obj:`int`): Unique identifier for the target private chat.
draft_id (:obj:`int`): Unique identifier of the message draft; must be non-zero.
Changes of drafts with the same identifier are animated.
text (:obj:`str`): Text of the message to be sent,
:tg-const:`telegram.constants.MessageLimit.MIN_TEXT_LENGTH`-
:tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` characters after
entities parsing.
parse_mode (:obj:`str`): |parse_mode|
entities (Sequence[:class:`telegram.MessageEntity`], optional): Sequence of special
entities that appear in message text, which can be specified instead of
:paramref:`parse_mode`.
|sequenceargs|
message_thread_id (:obj:`int`, optional): Unique identifier for the target
message thread.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"chat_id": chat_id,
"draft_id": draft_id,
"text": text,
"entities": entities,
}
return await self._send_message(
"sendMessageDraft",
data,
message_thread_id=message_thread_id,
parse_mode=parse_mode,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def delete_messages(
self,
chat_id: int | str,
@@ -1253,6 +1317,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1298,6 +1363,10 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
forwarded to a direct messages chat.
.. versionadded:: 22.4
message_effect_id (:obj:`str`, optional): Unique identifier of the message effect to be
added to the message; only available when forwarding to private chats
.. versionadded:: NEXT.VERSION
Returns:
:class:`telegram.Message`: On success, the sent Message is returned.
@@ -1325,6 +1394,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
direct_messages_topic_id=direct_messages_topic_id,
message_effect_id=message_effect_id,
)
async def forward_messages(
@@ -5905,7 +5975,9 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
can_invite_users (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
invite new users to the chat.
can_restrict_members (:obj:`bool`, optional): Pass :obj:`True`, if the administrator
can restrict, ban or unban chat members, or access supergroup statistics.
can restrict, ban or unban chat members, or access supergroup statistics. For
backward compatibility, defaults to :obj:`True` for promotions of channel
administrators.
can_pin_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
pin messages, for supergroups only.
can_promote_members (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
@@ -8347,6 +8419,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: int | None = None,
@@ -8408,6 +8481,10 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
direct_messages_topic_id (:obj:`int`, optional): |direct_messages_topic_id|
.. versionadded:: 22.4
message_effect_id (:obj:`str`, optional): Unique identifier of the message effect to be
added to the message; only available when copying to private chats
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@@ -8470,6 +8547,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"direct_messages_topic_id": direct_messages_topic_id,
"video_start_timestamp": video_start_timestamp,
"suggested_post_parameters": suggested_post_parameters,
"message_effect_id": message_effect_id,
}
result = await self._post(
@@ -8903,8 +8981,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs: JSONDict | None = None,
) -> bool:
"""
Use this method to edit name and icon of a topic in a forum supergroup chat. The bot must
be an administrator in the chat for this to work and must have the
Use this method to edit name and icon of a topic in a forum supergroup chat or a private
chat with a user. In the case of a supergroup chat the bot must be an administrator in the
chat for this to work and must have the
:paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights,
unless it is the creator of the topic.
@@ -9046,7 +9125,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
) -> bool:
"""
Use this method to delete a forum topic along with all its messages in a forum supergroup
chat. The bot must be an administrator in the chat for this to work and must have
chat or a private chat with a user. In the case of a supergroup chat the bot must be an
administrator in the chat for this to work and must have the
:paramref:`~telegram.ChatAdministratorRights.can_delete_messages` administrator rights.
.. versionadded:: 20.0
@@ -9088,10 +9168,11 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs: JSONDict | None = None,
) -> bool:
"""
Use this method to clear the list of pinned messages in a forum topic. The bot must
be an administrator in the chat for this to work and must have
:paramref:`~telegram.ChatAdministratorRights.can_pin_messages` administrator rights
in the supergroup.
Use this method to clear the list of pinned messages in a forum topic in a forum supergroup
chat or a private chat with a user. In the case of a supergroup chat the bot must be an
administrator in the chat for this to work and must have the
:paramref:`~telegram.ChatAdministratorRights.can_pin_messages` administrator right in
the supergroup.
.. versionadded:: 20.0
@@ -9861,11 +9942,15 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
exclude_unsaved: bool | None = None,
exclude_saved: bool | None = None,
exclude_unlimited: bool | None = None,
# tags: deprecated NEXT.VERSION; bot api 9.3
exclude_limited: bool | None = None,
exclude_unique: bool | None = None,
sort_by_price: bool | None = None,
offset: str | None = None,
limit: int | None = None,
exclude_limited_upgradable: bool | None = None,
exclude_limited_non_upgradable: bool | None = None,
exclude_from_blockchain: bool | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -9889,7 +9974,26 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
be purchased an unlimited number of times.
exclude_limited (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that can be
purchased a limited number of times.
.. deprecated:: NEXT.VERSION
Bot API 9.3 deprecated this parameter in favor of
:paramref:`exclude_limited_upgradabale` and
:paramref:`exclude_limited_non_upgradable`.
exclude_limited_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts
that can be purchased a limited number of times and can be upgraded to unique.
.. versionadded:: NEXT.VERSION
exclude_limited_non_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude
gifts that can be purchased a limited number of times and can't be upgraded to
unique
.. versionadded:: NEXT.VERSION
exclude_unique (:obj:`bool`, optional): Pass :obj:`True` to exclude unique gifts.
exclude_from_blockchain (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts
that were assigned from the TON blockchain and can't be resold or transferred in
Telegram.
.. versionadded:: NEXT.VERSION
sort_by_price (:obj:`bool`, optional): Pass :obj:`True` to sort results by gift price
instead of send date. Sorting is applied before pagination.
offset (:obj:`str`, optional): Offset of the first entry to return as received from
@@ -9905,13 +10009,29 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Raises:
:class:`telegram.error.TelegramError`
"""
if exclude_limited is not None:
self._warn(
PTBDeprecationWarning(
version="NEXT.VERSION",
message=build_deprecation_warning_message(
deprecated_name="exclude_limited",
new_name="exclude_limited_(non_)upgradable",
bot_api_version="9.3",
object_type="parameter",
),
),
stacklevel=2,
)
data: JSONDict = {
"business_connection_id": business_connection_id,
"exclude_unsaved": exclude_unsaved,
"exclude_saved": exclude_saved,
"exclude_unlimited": exclude_unlimited,
"exclude_limited": exclude_limited,
"exclude_limited_upgradable": exclude_limited_upgradable,
"exclude_limited_non_upgradable": exclude_limited_non_upgradable,
"exclude_unique": exclude_unique,
"exclude_from_blockchain": exclude_from_blockchain,
"sort_by_price": sort_by_price,
"offset": offset,
"limit": limit,
@@ -11233,7 +11353,7 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
Args:
gift_id (:obj:`str` | :class:`~telegram.Gift`): Identifier of the gift or a
:class:`~telegram.Gift` object
:class:`~telegram.Gift` object; limited gifts can't be sent to channel chats
user_id (:obj:`int`, optional): Required if :paramref:`chat_id` is not specified.
Unique identifier of the target user that will receive the gift.
@@ -11575,6 +11695,227 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
api_kwargs=api_kwargs,
)
async def repost_story(
self,
business_connection_id: str,
from_chat_id: int,
from_story_id: int,
active_period: TimePeriod,
post_to_chat_page: bool | None = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> Story:
"""
Reposts a story on behalf of a business account from another business account.
Both business accounts must be managed by the same bot, and the story on the source account
must have been posted (or reposted) by the bot. Requires the
:attr:`~telegram.BusinessBotRight.can_manage_stories` business bot right for both
business accounts.
.. versionadded:: NEXT.VERSION
Args:
business_connection_id (:obj:`str`): Unique identifier of the business
from_chat_id (:obj:`int`): Unique identifier of the chat which posted the story that
should be reposted
from_story_id (:obj:`int`): Unique identifier of the story that should be reposted
active_period (:obj:`int` | :class:`datetime.timedelta`): Period after which the story
is moved to the archive, in seconds; must be one of
:tg-const:`telegram.constants.StoryLimit.SIX_HOURS`,
:tg-const:`telegram.constants.StoryLimit.TWELVE_HOURS`,
:tg-const:`telegram.constants.StoryLimit.ONE_DAY`, or
:tg-const:`telegram.constants.StoryLimit.TWO_DAYS`.
post_to_chat_page (:obj:`bool`, optional): Pass :obj:`True` to keep the story
accessible after it expires.
protect_content (:obj:`bool`, optional): Pass :obj:`True` if the content of the story
must be protected from forwarding and screenshotting
Returns:
:class:`telegram.Story`
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"business_connection_id": business_connection_id,
"from_chat_id": from_chat_id,
"from_story_id": from_story_id,
"active_period": active_period,
"post_to_chat_page": post_to_chat_page,
"protect_content": protect_content,
}
return Story.de_json(
data=await self._post(
"repostStory",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
),
bot=self,
)
async def get_user_gifts(
self,
user_id: int,
exclude_unlimited: bool | None = None,
exclude_limited_upgradable: bool | None = None,
exclude_limited_non_upgradable: bool | None = None,
exclude_from_blockchain: bool | None = None,
exclude_unique: bool | None = None,
sort_by_price: bool | None = None,
offset: str | None = None,
limit: int | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> OwnedGifts:
"""Returns the gifts owned and hosted by a user.
.. versionadded:: NEXT.VERSION
user_id (:obj:`int`): Unique identifier of the user
exclude_unlimited (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that can be
purchased an unlimited number of times
exclude_limited_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
can be purchased a limited number of times and can be upgraded to unique
exclude_limited_non_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts
that can be purchased a limited number of times and can't be upgraded to unique
exclude_from_blockchain (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
were assigned from the TON blockchain and can't be resold or transferred in Telegram
exclude_unique (:obj:`bool`, optional): Pass :obj:`True` to exclude unique gifts
sort_by_price (:obj:`bool`, optional): Pass :obj:`True` to sort results by gift price
instead of send date. Sorting is applied before pagination.
offset (:obj:`str`, optional): Offset of the first entry to return as received from the
previous request; use an empty string to get the first chunk of results
limit (:obj:`int`, optional): The maximum number of gifts to be returned;
:tg-const:`~telegram.constants.BusinessLimit.MIN_GIFT_RESULTS` -
:tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESLUTS`.
Defaults to :tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESLUTS`
Returns:
:class:`telegram.OwnedGifts`: The owned gifts for the user.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"user_id": user_id,
"exclude_unlimited": exclude_unlimited,
"exclude_limited_upgradable": exclude_limited_upgradable,
"exclude_limited_non_upgradable": exclude_limited_non_upgradable,
"exclude_from_blockchain": exclude_from_blockchain,
"exclude_unique": exclude_unique,
"sort_by_price": sort_by_price,
"offset": offset,
"limit": limit,
}
result = await self._post(
"getUserGifts",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return OwnedGifts.de_json(result, self)
async def get_chat_gifts(
self,
chat_id: int | str,
exclude_unsaved: bool | None = None,
exclude_saved: bool | None = None,
exclude_unlimited: bool | None = None,
exclude_limited_upgradable: bool | None = None,
exclude_limited_non_upgradable: bool | None = None,
exclude_from_blockchain: bool | None = None,
exclude_unique: bool | None = None,
sort_by_price: bool | None = None,
offset: str | None = None,
limit: int | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> OwnedGifts:
"""Use this method to get gifts owned by a chat.
.. versionadded:: NEXT.VERSION
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
exclude_unsaved (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that aren't
saved to the chat's profile page. Always :obj:`True`, unless the bot has the
:attr:`~telegram.ChatAdministratorRights..can_post_messages` administrator right in the
channel.
exclude_saved (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that are saved to
the chat's profile page. Always :obj:`False`, unless the bot has the
:attr:`~telegram.ChatAdministratorRights..can_post_messages` administrator right in the
channel.
exclude_unlimited (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that can be
purchased an unlimited number of times
exclude_limited_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
can be purchased a limited number of times and can be upgraded to unique
exclude_limited_non_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts
that can be purchased a limited number of times and can't be upgraded to unique
exclude_from_blockchain (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
were assigned from the TON blockchain and can't be resold or transferred in Telegram
exclude_unique (:obj:`bool`, optional): Pass :obj:`True` to exclude unique gifts
sort_by_price (:obj:`bool`, optional): Pass :obj:`True` to sort results by gift price
instead of send date. Sorting is applied before pagination.
offset (:obj:`str`, optional): Offset of the first entry to return as received from the
previous request; use an empty string to get the first chunk of results
limit (:obj:`int`, optional): The maximum number of gifts to be returned;
:tg-const:`~telegram.constants.BusinessLimit.MIN_GIFT_RESULTS` -
:tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESLUTS`.
Defaults to :tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESLUTS`
Returns:
:class:`telegram.OwnedGifts`: The owned gifts for the chat.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"chat_id": chat_id,
"exclude_unsaved": exclude_unsaved,
"exclude_saved": exclude_saved,
"exclude_unlimited": exclude_unlimited,
"exclude_limited_upgradable": exclude_limited_upgradable,
"exclude_limited_non_upgradable": exclude_limited_non_upgradable,
"exclude_from_blockchain": exclude_from_blockchain,
"exclude_unique": exclude_unique,
"sort_by_price": sort_by_price,
"offset": offset,
"limit": limit,
}
result = await self._post(
"getChatGifts",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return OwnedGifts.de_json(result, self)
def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@@ -11589,6 +11930,8 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
"""Alias for :meth:`get_me`"""
sendMessage = send_message
"""Alias for :meth:`send_message`"""
sendMessageDraft = send_message_draft
"""Alias for :meth:`send_message_draft`"""
deleteMessage = delete_message
"""Alias for :meth:`delete_message`"""
deleteMessages = delete_messages
@@ -11899,3 +12242,9 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
"""Alias for :meth:`approve_suggested_post`"""
declineSuggestedPost = decline_suggested_post
"""Alias for :meth:`decline_suggested_post`"""
repostStory = repost_story
"""Alias for :meth:`repost_story`"""
getUserGifts = get_user_gifts
"""Alias for :meth:`get_user_gifts`"""
getChatGifts = get_chat_gifts
"""Alias for :meth:`get_chat_gifts`"""
+2
View File
@@ -874,6 +874,7 @@ class CallbackQuery(TelegramObject):
allow_paid_broadcast: bool | None = None,
video_start_timestamp: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: int | None = None,
@@ -925,6 +926,7 @@ class CallbackQuery(TelegramObject):
show_caption_above_media=show_caption_above_media,
allow_paid_broadcast=allow_paid_broadcast,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
MAX_ANSWER_TEXT_LENGTH: Final[int] = (
+141
View File
@@ -67,9 +67,11 @@ if TYPE_CHECKING:
Message,
MessageEntity,
MessageId,
OwnedGifts,
PhotoSize,
ReplyParameters,
Sticker,
Story,
SuggestedPostParameters,
UserChatBoosts,
Venue,
@@ -1080,6 +1082,44 @@ class _ChatBase(TelegramObject):
suggested_post_parameters=suggested_post_parameters,
)
async def send_message_draft(
self,
draft_id: int,
text: str,
message_thread_id: int | None = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
entities: Sequence["MessageEntity"] | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> bool:
"""Shortcut for::
await bot.send_message_draft(update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see :meth:`telegram.Bot.send_message_draft`.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().send_message_draft(
chat_id=self.id,
draft_id=draft_id,
text=text,
message_thread_id=message_thread_id,
parse_mode=parse_mode,
entities=entities,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def delete_message(
self,
message_id: int,
@@ -2323,6 +2363,7 @@ class _ChatBase(TelegramObject):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
reply_to_message_id: int | None = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2368,6 +2409,7 @@ class _ChatBase(TelegramObject):
allow_paid_broadcast=allow_paid_broadcast,
direct_messages_topic_id=direct_messages_topic_id,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def copy_message(
@@ -2387,6 +2429,7 @@ class _ChatBase(TelegramObject):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
reply_to_message_id: int | None = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2432,6 +2475,7 @@ class _ChatBase(TelegramObject):
allow_paid_broadcast=allow_paid_broadcast,
direct_messages_topic_id=direct_messages_topic_id,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def send_copies(
@@ -2538,6 +2582,7 @@ class _ChatBase(TelegramObject):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2574,6 +2619,7 @@ class _ChatBase(TelegramObject):
message_thread_id=message_thread_id,
direct_messages_topic_id=direct_messages_topic_id,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def forward_to(
@@ -2586,6 +2632,7 @@ class _ChatBase(TelegramObject):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2623,6 +2670,7 @@ class _ChatBase(TelegramObject):
message_thread_id=message_thread_id,
direct_messages_topic_id=direct_messages_topic_id,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def forward_messages_from(
@@ -3854,6 +3902,99 @@ class _ChatBase(TelegramObject):
api_kwargs=api_kwargs,
)
async def repost_story(
self,
business_connection_id: str,
from_story_id: int,
active_period: TimePeriod,
post_to_chat_page: bool | None = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> "Story":
"""Shortcut for::
await bot.repost_story(
from_chat_id=update.effective_chat.id,
*args, **kwargs
)
For the documentation of the arguments, please see :meth:`telegram.Bot.repost_story`.
.. versionadded:: NEXT.VERSION
Returns:
:class:`Story`: On success, :class:`Story` is returned.
"""
return await self.get_bot().repost_story(
business_connection_id=business_connection_id,
from_chat_id=self.id,
from_story_id=from_story_id,
active_period=active_period,
post_to_chat_page=post_to_chat_page,
protect_content=protect_content,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def get_gifts(
self,
exclude_unsaved: bool | None = None,
exclude_saved: bool | None = None,
exclude_unlimited: bool | None = None,
exclude_limited_upgradable: bool | None = None,
exclude_limited_non_upgradable: bool | None = None,
exclude_from_blockchain: bool | None = None,
exclude_unique: bool | None = None,
sort_by_price: bool | None = None,
offset: str | None = None,
limit: int | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> "OwnedGifts":
"""Shortcut for::
await bot.get_chat_gifts(chat_id=update.effective_chat.id)
For the documentation of the arguments, please see
:meth:`telegram.Bot.get_chat_gifts`.
.. versionadded:: NEXT.VERSION
Returns:
:class:`telegram.OwnedGifts`: On success, returns the gifts owned by the chat.
"""
return await self.get_bot().get_chat_gifts(
chat_id=self.id,
exclude_unsaved=exclude_unsaved,
exclude_saved=exclude_saved,
exclude_unlimited=exclude_unlimited,
exclude_limited_upgradable=exclude_limited_upgradable,
exclude_limited_non_upgradable=exclude_limited_non_upgradable,
exclude_from_blockchain=exclude_from_blockchain,
exclude_unique=exclude_unique,
sort_by_price=sort_by_price,
offset=offset,
limit=limit,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
class Chat(_ChatBase):
"""This object represents a chat.
+42
View File
@@ -30,6 +30,8 @@ from telegram._chatpermissions import ChatPermissions
from telegram._files.chatphoto import ChatPhoto
from telegram._gifts import AcceptedGiftTypes
from telegram._reaction import ReactionType
from telegram._uniquegift import UniqueGiftColors
from telegram._userrating import UserRating
from telegram._utils.argumentparsing import (
de_json_optional,
de_list_optional,
@@ -232,6 +234,19 @@ class ChatFullInfo(_ChatBase):
chat; for direct messages chats only.
.. versionadded:: 22.4
rating (:class:`telegram.UserRating`, optional): For private chats, the rating of the user
if any.
.. versionadded:: NEXT.VERSION
unique_gift_colors (:class:`telegram.UniqueGiftColors`, optional): The color scheme based
on a unique gift that must be used for the chat's name, message replies and link
previews
.. versionadded:: NEXT.VERSION
paid_message_star_count (:obj:`int`, optional): The number of Telegram Stars a general user
have to pay to send a message to the chat
.. versionadded:: NEXT.VERSION
Attributes:
id (:obj:`int`): Unique identifier for this chat.
@@ -404,6 +419,19 @@ class ChatFullInfo(_ChatBase):
chat; for direct messages chats only.
.. versionadded:: 22.4
rating (:class:`telegram.UserRating`): Optional. For private chats, the rating of the user
if any.
.. versionadded:: NEXT.VERSION
unique_gift_colors (:class:`telegram.UniqueGiftColors`): Optional. The color scheme based
on a unique gift that must be used for the chat's name, message replies and link
previews
.. versionadded:: NEXT.VERSION
paid_message_star_count (:obj:`int`): Optional. The number of Telegram Stars a general user
have to pay to send a message to the chat
.. versionadded:: NEXT.VERSION
.. _accent colors: https://core.telegram.org/bots/api#accent-colors
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
@@ -440,6 +468,7 @@ class ChatFullInfo(_ChatBase):
"linked_chat_id",
"location",
"max_reaction_count",
"paid_message_star_count",
"parent_chat",
"permissions",
"personal_chat",
@@ -447,7 +476,9 @@ class ChatFullInfo(_ChatBase):
"pinned_message",
"profile_accent_color_id",
"profile_background_custom_emoji_id",
"rating",
"sticker_set_name",
"unique_gift_colors",
"unrestrict_boost_count",
)
@@ -500,6 +531,9 @@ class ChatFullInfo(_ChatBase):
can_send_paid_media: bool | None = None,
is_direct_messages: bool | None = None,
parent_chat: Chat | None = None,
rating: UserRating | None = None,
unique_gift_colors: UniqueGiftColors | None = None,
paid_message_star_count: int | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -563,6 +597,9 @@ class ChatFullInfo(_ChatBase):
self.can_send_paid_media: bool | None = can_send_paid_media
self.accepted_gift_types: AcceptedGiftTypes = accepted_gift_types
self.parent_chat: Chat | None = parent_chat
self.rating: UserRating | None = rating
self.unique_gift_colors: UniqueGiftColors | None = unique_gift_colors
self.paid_message_star_count: int | None = paid_message_star_count
@property
def slow_mode_delay(self) -> int | dtm.timedelta | None:
@@ -615,4 +652,9 @@ class ChatFullInfo(_ChatBase):
)
data["parent_chat"] = de_json_optional(data.get("parent_chat"), Chat, bot)
data["rating"] = de_json_optional(data.get("rating"), UserRating, bot)
data["unique_gift_colors"] = de_json_optional(
data.get("unique_gift_colors"), UniqueGiftColors, bot
)
return super().de_json(data=data, bot=bot)
+13
View File
@@ -22,6 +22,7 @@ import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._chat import Chat
from telegram._messageentity import MessageEntity
from telegram._telegramobject import TelegramObject
from telegram._user import User
@@ -51,6 +52,10 @@ class ChecklistTask(TelegramObject):
entities that appear in the task text.
completed_by_user (:class:`telegram.User`, optional): User that completed the task; omitted
if the task wasn't completed
completed_by_chat (:class:`telegram.Chat`, optional): Chat that completed the task; omitted
if the task wasn't completed by a chat
.. versionadded:: NEXT.VERSION
completion_date (:class:`datetime.datetime`, optional): Point in time when
the task was completed; :attr:`~telegram.constants.ZERO_DATE` if the task wasn't
completed
@@ -64,6 +69,10 @@ class ChecklistTask(TelegramObject):
entities that appear in the task text.
completed_by_user (:class:`telegram.User`): Optional. User that completed the task; omitted
if the task wasn't completed
completed_by_chat (:class:`telegram.Chat`): Optional. Chat that completed the task; omitted
if the task wasn't completed by a chat
.. versionadded:: NEXT.VERSION
completion_date (:class:`datetime.datetime`): Optional. Point in time when
the task was completed; :attr:`~telegram.constants.ZERO_DATE` if the task wasn't
completed
@@ -72,6 +81,7 @@ class ChecklistTask(TelegramObject):
"""
__slots__ = (
"completed_by_chat",
"completed_by_user",
"completion_date",
"id",
@@ -86,6 +96,7 @@ class ChecklistTask(TelegramObject):
text_entities: Sequence[MessageEntity] | None = None,
completed_by_user: User | None = None,
completion_date: dtm.datetime | None = None,
completed_by_chat: Chat | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -94,6 +105,7 @@ class ChecklistTask(TelegramObject):
self.text: str = text
self.text_entities: tuple[MessageEntity, ...] = parse_sequence_arg(text_entities)
self.completed_by_user: User | None = completed_by_user
self.completed_by_chat: Chat | None = completed_by_chat
self.completion_date: dtm.datetime | None = completion_date
self._id_attrs = (self.id,)
@@ -114,6 +126,7 @@ class ChecklistTask(TelegramObject):
data["completion_date"] = from_timestamp(date, tzinfo=loc_tzinfo)
data["completed_by_user"] = de_json_optional(data.get("completed_by_user"), User, bot)
data["completed_by_chat"] = de_json_optional(data.get("completed_by_chat"), Chat, bot)
data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot)
return super().de_json(data=data, bot=bot)
+28 -2
View File
@@ -38,6 +38,10 @@ class ForumTopic(TelegramObject):
icon_color (:obj:`int`): Color of the topic icon in RGB format
icon_custom_emoji_id (:obj:`str`, optional): Unique identifier of the custom emoji shown
as the topic icon.
is_name_implicit (:obj:`bool`, optional): :obj:`True`, if the name of the topic wasn't
specified explicitly by its creator and likely needs to be changed by the bot.
.. versionadded:: NEXT.VERSION
Attributes:
message_thread_id (:obj:`int`): Unique identifier of the forum topic
@@ -45,9 +49,19 @@ class ForumTopic(TelegramObject):
icon_color (:obj:`int`): Color of the topic icon in RGB format
icon_custom_emoji_id (:obj:`str`): Optional. Unique identifier of the custom emoji shown
as the topic icon.
is_name_implicit (:obj:`bool`): Optional. :obj:`True`, if the name of the topic wasn't
specified explicitly by its creator and likely needs to be changed by the bot.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("icon_color", "icon_custom_emoji_id", "message_thread_id", "name")
__slots__ = (
"icon_color",
"icon_custom_emoji_id",
"is_name_implicit",
"message_thread_id",
"name",
)
def __init__(
self,
@@ -55,6 +69,7 @@ class ForumTopic(TelegramObject):
name: str,
icon_color: int,
icon_custom_emoji_id: str | None = None,
is_name_implicit: bool | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -63,6 +78,7 @@ class ForumTopic(TelegramObject):
self.name: str = name
self.icon_color: int = icon_color
self.icon_custom_emoji_id: str | None = icon_custom_emoji_id
self.is_name_implicit: bool | None = is_name_implicit
self._id_attrs = (self.message_thread_id, self.name, self.icon_color)
@@ -84,21 +100,30 @@ class ForumTopicCreated(TelegramObject):
icon_color (:obj:`int`): Color of the topic icon in RGB format
icon_custom_emoji_id (:obj:`str`, optional): Unique identifier of the custom emoji shown
as the topic icon.
is_name_implicit (:obj:`bool`, optional): :obj:`True`, if the name of the topic wasn't
specified explicitly by its creator and likely needs to be changed by the bot.
.. versionadded:: NEXT.VERSION
Attributes:
name (:obj:`str`): Name of the topic
icon_color (:obj:`int`): Color of the topic icon in RGB format
icon_custom_emoji_id (:obj:`str`): Optional. Unique identifier of the custom emoji shown
as the topic icon.
is_name_implicit (:obj:`bool`): Optional. :obj:`True`, if the name of the topic wasn't
specified explicitly by its creator and likely needs to be changed by the bot.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("icon_color", "icon_custom_emoji_id", "name")
__slots__ = ("icon_color", "icon_custom_emoji_id", "is_name_implicit", "name")
def __init__(
self,
name: str,
icon_color: int,
icon_custom_emoji_id: str | None = None,
is_name_implicit: bool | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -106,6 +131,7 @@ class ForumTopicCreated(TelegramObject):
self.name: str = name
self.icon_color: int = icon_color
self.icon_custom_emoji_id: str | None = icon_custom_emoji_id
self.is_name_implicit: bool | None = is_name_implicit
self._id_attrs = (self.name, self.icon_color)
+157 -7
View File
@@ -34,6 +34,55 @@ if TYPE_CHECKING:
from telegram import Bot
class GiftBackground(TelegramObject):
"""This object describes the background of a gift.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal if their :attr:`center_color`, :attr:`edge_color` and :attr:`text_color` are
equal.
.. versionadded:: NEXT.VERSION
Args:
center_color (:obj:`int`): Center color of the background in RGB format.
edge_color (:obj:`int`): Edge color of the background in RGB format.
text_color (:obj:`int`): Text color of the background in RGB format.
Attributes:
center_color (:obj:`int`): Center color of the background in RGB format.
edge_color (:obj:`int`): Edge color of the background in RGB format.
text_color (:obj:`int`): Text color of the background in RGB format.
"""
__slots__ = (
"center_color",
"edge_color",
"text_color",
)
def __init__(
self,
center_color: int,
edge_color: int,
text_color: int,
*,
api_kwargs: JSONDict | None = None,
):
super().__init__(api_kwargs=api_kwargs)
self.center_color: int = center_color
self.edge_color: int = edge_color
self.text_color: int = text_color
self._id_attrs = (
self.center_color,
self.edge_color,
self.text_color,
)
self._freeze()
class Gift(TelegramObject):
"""This object represents a gift that can be sent by the bot.
@@ -48,9 +97,9 @@ class Gift(TelegramObject):
star_count (:obj:`int`): The number of Telegram Stars that must be paid to send the
sticker.
total_count (:obj:`int`, optional): The total number of the gifts of this type that can be
sent; for limited gifts only.
sent by all users; for limited gifts only.
remaining_count (:obj:`int`, optional): The number of remaining gifts of this type that can
be sent; for limited gifts only.
be sent by all users; for limited gifts only.
upgrade_star_count (:obj:`int`, optional): The number of Telegram Stars that must be paid
to upgrade the gift to a unique one.
@@ -59,6 +108,29 @@ class Gift(TelegramObject):
published the gift.
.. versionadded:: 22.4
personal_total_count (:obj:`int`, optional): The total number of gifts of this type that
can be sent by the bot; for limited gifts only.
.. versionadded:: NEXT.VERSION
personal_remaining_count (:obj:`int`, optional): The number of remaining gifts of this type
that can be sent by the bot; for limited gifts only.
.. versionadded:: NEXT.VERSION
background (:class:`GiftBackground`, optional): Background of the gift.
.. versionadded:: NEXT.VERSION
is_premium (:obj:`bool`, optional): :obj:`True`, if the gift can only be purchased by
Telegram Premium subscribers.
.. versionadded:: NEXT.VERSION
has_colors (:obj:`bool`, optional): :obj:`True`, if the gift can be used (after being
upgraded) to customize a user's appearance.
.. versionadded:: NEXT.VERSION
unique_gift_variant_count (:obj:`int`, optional): The total number of different unique
gifts that can be obtained by upgrading the gift.
.. versionadded:: NEXT.VERSION
Attributes:
id (:obj:`str`): Unique identifier of the gift.
@@ -66,9 +138,9 @@ class Gift(TelegramObject):
star_count (:obj:`int`): The number of Telegram Stars that must be paid to send the
sticker.
total_count (:obj:`int`): Optional. The total number of the gifts of this type that can be
sent; for limited gifts only.
sent by all users; for limited gifts only.
remaining_count (:obj:`int`): Optional. The number of remaining gifts of this type that can
be sent; for limited gifts only.
be sent by all users; for limited gifts only.
upgrade_star_count (:obj:`int`): Optional. The number of Telegram Stars that must be paid
to upgrade the gift to a unique one.
@@ -77,16 +149,45 @@ class Gift(TelegramObject):
published the gift.
.. versionadded:: 22.4
personal_total_count (:obj:`int`): Optional. The total number of gifts of this type that
can be sent by the bot; for limited gifts only.
.. versionadded:: NEXT.VERSION
personal_remaining_count (:obj:`int`): Optional. The number of remaining gifts of this type
that can be sent by the bot; for limited gifts only.
.. versionadded:: NEXT.VERSION
background (:class:`GiftBackground`): Optional. Background of the gift.
.. versionadded:: NEXT.VERSION
is_premium (:obj:`bool`): Optional. :obj:`True`, if the gift can only be purchased by
Telegram Premium subscribers.
.. versionadded:: NEXT.VERSION
has_colors (:obj:`bool`): Optional. :obj:`True`, if the gift can be used (after being
upgraded) to customize a user's appearance.
.. versionadded:: NEXT.VERSION
unique_gift_variant_count (:obj:`int`): Optional. The total number of different unique
gifts that can be obtained by upgrading the gift.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
"background",
"has_colors",
"id",
"is_premium",
"personal_remaining_count",
"personal_total_count",
"publisher_chat",
"remaining_count",
"star_count",
"sticker",
"total_count",
"unique_gift_variant_count",
"upgrade_star_count",
)
@@ -99,6 +200,12 @@ class Gift(TelegramObject):
remaining_count: int | None = None,
upgrade_star_count: int | None = None,
publisher_chat: Chat | None = None,
personal_total_count: int | None = None,
personal_remaining_count: int | None = None,
background: GiftBackground | None = None,
is_premium: bool | None = None,
has_colors: bool | None = None,
unique_gift_variant_count: int | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -110,6 +217,12 @@ class Gift(TelegramObject):
self.remaining_count: int | None = remaining_count
self.upgrade_star_count: int | None = upgrade_star_count
self.publisher_chat: Chat | None = publisher_chat
self.personal_total_count: int | None = personal_total_count
self.personal_remaining_count: int | None = personal_remaining_count
self.background: GiftBackground | None = background
self.is_premium: bool | None = is_premium
self.has_colors: bool | None = has_colors
self.unique_gift_variant_count: int | None = unique_gift_variant_count
self._id_attrs = (self.id,)
@@ -122,6 +235,7 @@ class Gift(TelegramObject):
data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot)
data["publisher_chat"] = de_json_optional(data.get("publisher_chat"), Chat, bot)
data["background"] = de_json_optional(data.get("background"), GiftBackground, bot)
return super().de_json(data=data, bot=bot)
@@ -181,7 +295,7 @@ class GiftInfo(TelegramObject):
the receiver by converting the gift; omitted if conversion to Telegram Stars
is impossible.
prepaid_upgrade_star_count (:obj:`int`, optional): Number of Telegram Stars that were
prepaid by the sender for the ability to upgrade the gift.
prepaid for the ability to upgrade the gift.
can_be_upgraded (:obj:`bool`, optional): :obj:`True`, if the gift can be upgraded
to a unique gift.
text (:obj:`str`, optional): Text of the message that was added to the gift.
@@ -189,6 +303,14 @@ class GiftInfo(TelegramObject):
appear in the text.
is_private (:obj:`bool`, optional): :obj:`True`, if the sender and gift text are
shown only to the gift receiver; otherwise, everyone will be able to see them.
is_upgrade_separate (:obj:`bool`, optional): :obj:`True`, if the gift's upgrade was
purchased after the gift was sent.
.. versionadded:: NEXT.VERSION
unique_gift_number (:obj:`int`, optional): Unique number reserved for this gift when
upgraded. See the number field in :class:`~telegram.UniqueGift`.
.. versionadded:: NEXT.VERSION
Attributes:
gift (:class:`Gift`): Information about the gift.
@@ -198,7 +320,7 @@ class GiftInfo(TelegramObject):
the receiver by converting the gift; omitted if conversion to Telegram Stars
is impossible.
prepaid_upgrade_star_count (:obj:`int`): Optional. Number of Telegram Stars that were
prepaid by the sender for the ability to upgrade the gift.
prepaid for the ability to upgrade the gift.
can_be_upgraded (:obj:`bool`): Optional. :obj:`True`, if the gift can be upgraded
to a unique gift.
text (:obj:`str`): Optional. Text of the message that was added to the gift.
@@ -206,6 +328,14 @@ class GiftInfo(TelegramObject):
appear in the text.
is_private (:obj:`bool`): Optional. :obj:`True`, if the sender and gift text are
shown only to the gift receiver; otherwise, everyone will be able to see them.
is_upgrade_separate (:obj:`bool`): Optional. :obj:`True`, if the gift's upgrade was
purchased after the gift was sent.
.. versionadded:: NEXT.VERSION
unique_gift_number (:obj:`int`): Optional. Unique number reserved for this gift when
upgraded. See the number field in :class:`~telegram.UniqueGift`.
.. versionadded:: NEXT.VERSION
"""
@@ -215,9 +345,11 @@ class GiftInfo(TelegramObject):
"entities",
"gift",
"is_private",
"is_upgrade_separate",
"owned_gift_id",
"prepaid_upgrade_star_count",
"text",
"unique_gift_number",
)
def __init__(
@@ -230,6 +362,8 @@ class GiftInfo(TelegramObject):
text: str | None = None,
entities: Sequence[MessageEntity] | None = None,
is_private: bool | None = None,
unique_gift_number: int | None = None,
is_upgrade_separate: bool | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -244,6 +378,8 @@ class GiftInfo(TelegramObject):
self.text: str | None = text
self.entities: tuple[MessageEntity, ...] = parse_sequence_arg(entities)
self.is_private: bool | None = is_private
self.unique_gift_number: int | None = unique_gift_number
self.is_upgrade_separate: bool | None = is_upgrade_separate
self._id_attrs = (self.gift,)
@@ -319,9 +455,11 @@ class AcceptedGiftTypes(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal if their :attr:`unlimited_gifts`, :attr:`limited_gifts`,
:attr:`unique_gifts` and :attr:`premium_subscription` are equal.
:attr:`unique_gifts`, :attr:`premium_subscription` and :attr:`gifts_from_channels` are equal.
.. versionadded:: 22.1
.. versionchanged:: NEXT.VERSION
:attr:`gifts_from_channels` is now considered for equality checks.
Args:
unlimited_gifts (:class:`bool`): :obj:`True`, if unlimited regular gifts are accepted.
@@ -330,6 +468,10 @@ class AcceptedGiftTypes(TelegramObject):
to unique for free are accepted.
premium_subscription (:class:`bool`): :obj:`True`, if a Telegram Premium subscription
is accepted.
gifts_from_channels (:obj:`bool`): :obj:`True`, if transfers of unique gifts from channels
are accepted
.. versionadded:: NEXT.VERSION
Attributes:
unlimited_gifts (:class:`bool`): :obj:`True`, if unlimited regular gifts are accepted.
@@ -338,10 +480,15 @@ class AcceptedGiftTypes(TelegramObject):
to unique for free are accepted.
premium_subscription (:class:`bool`): :obj:`True`, if a Telegram Premium subscription
is accepted.
gifts_from_channels (:obj:`bool`): :obj:`True`, if transfers of unique gifts from channels
are accepted
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
"gifts_from_channels",
"limited_gifts",
"premium_subscription",
"unique_gifts",
@@ -354,6 +501,7 @@ class AcceptedGiftTypes(TelegramObject):
limited_gifts: bool,
unique_gifts: bool,
premium_subscription: bool,
gifts_from_channels: bool,
*,
api_kwargs: JSONDict | None = None,
):
@@ -362,12 +510,14 @@ class AcceptedGiftTypes(TelegramObject):
self.limited_gifts: bool = limited_gifts
self.unique_gifts: bool = unique_gifts
self.premium_subscription: bool = premium_subscription
self.gifts_from_channels: bool = gifts_from_channels
self._id_attrs = (
self.unlimited_gifts,
self.limited_gifts,
self.unique_gifts,
self.premium_subscription,
self.gifts_from_channels,
)
self._freeze()
+78 -11
View File
@@ -496,12 +496,12 @@ class Message(MaybeInaccessibleMessage):
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. :paramref:`~telegram.InlineKeyboardButton.login_url` buttons are
represented as ordinary url buttons.
is_topic_message (:obj:`bool`, optional): :obj:`True`, if the message is sent to a forum
topic.
is_topic_message (:obj:`bool`, optional): :obj:`True`, if the message is sent to a topic
in a forum supergroup or a private chat with the bot.
.. versionadded:: 20.0
message_thread_id (:obj:`int`, optional): Unique identifier of a message thread to which
the message belongs; for supergroups only.
message_thread_id (:obj:`int`, optional): Unique identifier of a message thread or forum
topic to which the message belongs; for supergroups and private chats only.
.. versionadded:: 20.0
forum_topic_created (:class:`telegram.ForumTopicCreated`, optional): Service message:
@@ -558,6 +558,10 @@ class Message(MaybeInaccessibleMessage):
was sent or received
.. versionadded:: 22.1
gift_upgrade_sent (:class:`telegram.GiftInfo`, optional): Service message: upgrade of a
gift was purchased after the gift was sent
.. versionadded:: NEXT.VERSION
giveaway_created (:class:`telegram.GiveawayCreated`, optional): Service message: a
scheduled giveaway was created
@@ -898,12 +902,12 @@ class Message(MaybeInaccessibleMessage):
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. :paramref:`~telegram.InlineKeyboardButton.login_url` buttons are
represented as ordinary url buttons.
is_topic_message (:obj:`bool`): Optional. :obj:`True`, if the message is sent to a forum
topic.
is_topic_message (:obj:`bool`): Optional. :obj:`True`, if the message is sent to a topic
in a forum supergroup or a private chat with the bot.
.. versionadded:: 20.0
message_thread_id (:obj:`int`): Optional. Unique identifier of a message thread to which
the message belongs; for supergroups only.
message_thread_id (:obj:`int`): Optional. Unique identifier of a message thread or forum
topic to which the message belongs; for supergroups and private chats only.
.. versionadded:: 20.0
forum_topic_created (:class:`telegram.ForumTopicCreated`): Optional. Service message:
@@ -957,6 +961,10 @@ class Message(MaybeInaccessibleMessage):
was sent or received
.. versionadded:: 22.1
gift_upgrade_sent (:class:`telegram.GiftInfo`): Optional. Service message: upgrade of a
gift was purchased after the gift was sent
.. versionadded:: NEXT.VERSION
giveaway_created (:class:`telegram.GiveawayCreated`): Optional. Service message: a
scheduled giveaway was created
@@ -1125,6 +1133,7 @@ class Message(MaybeInaccessibleMessage):
"general_forum_topic_hidden",
"general_forum_topic_unhidden",
"gift",
"gift_upgrade_sent",
"giveaway",
"giveaway_completed",
"giveaway_created",
@@ -1296,6 +1305,7 @@ class Message(MaybeInaccessibleMessage):
suggested_post_info: "SuggestedPostInfo | None" = None,
suggested_post_approved: "SuggestedPostApproved | None" = None,
suggested_post_approval_failed: "SuggestedPostApprovalFailed | None" = None,
gift_upgrade_sent: GiftInfo | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -1422,6 +1432,7 @@ class Message(MaybeInaccessibleMessage):
self.suggested_post_approval_failed: SuggestedPostApprovalFailed | None = (
suggested_post_approval_failed
)
self.gift_upgrade_sent: GiftInfo | None = gift_upgrade_sent
self._effective_attachment = DEFAULT_NONE
@@ -1637,6 +1648,7 @@ class Message(MaybeInaccessibleMessage):
data["suggested_post_approval_failed"] = de_json_optional(
data.get("suggested_post_approval_failed"), SuggestedPostApprovalFailed, bot
)
data["gift_upgrade_sent"] = de_json_optional(data.get("gift_upgrade_sent"), GiftInfo, bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
@@ -1982,9 +1994,9 @@ class Message(MaybeInaccessibleMessage):
return message_thread_id
# self.message_thread_id can be used for send_*.param.message_thread_id only if the
# thread is a forum topic. It does not work if the thread is a chain of replies to a
# message in a normal group. In that case, self.message_thread_id is just the message_id
# of the first message in the chain.
# thread is a forum topic (in supergroups or private chats). It does not work if the
# thread is a chain of replies to a message in a normal group. In that case,
# self.message_thread_id is just the message_id of the first message in the chain.
if not self.is_topic_message:
return None
@@ -2077,6 +2089,55 @@ class Message(MaybeInaccessibleMessage):
suggested_post_parameters=suggested_post_parameters,
)
async def reply_text_draft(
self,
draft_id: int,
text: str,
parse_mode: ODVInput[str] = DEFAULT_NONE,
entities: Sequence["MessageEntity"] | None = None,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> bool:
"""Shortcut for::
await bot.send_message_draft(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
*args,
**kwargs,
)
For the documentation of the arguments, please see :meth:`telegram.Bot.send_message_draft`.
Note:
|reply_same_thread|
.. versionadded:: NEXT.VERSION
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
message_thread_id = self._parse_message_thread_id(self.chat_id, message_thread_id)
return await self.get_bot().send_message_draft(
chat_id=self.chat_id,
draft_id=draft_id,
text=text,
parse_mode=parse_mode,
entities=entities,
message_thread_id=message_thread_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def reply_markdown(
self,
text: str,
@@ -3822,6 +3883,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id: int | None = None,
video_start_timestamp: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3868,6 +3930,7 @@ class Message(MaybeInaccessibleMessage):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
direct_messages_topic_id=self._extract_direct_messages_topic_id(),
message_effect_id=message_effect_id,
)
async def copy(
@@ -3885,6 +3948,7 @@ class Message(MaybeInaccessibleMessage):
allow_paid_broadcast: bool | None = None,
video_start_timestamp: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
reply_to_message_id: int | None = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -3935,6 +3999,7 @@ class Message(MaybeInaccessibleMessage):
allow_paid_broadcast=allow_paid_broadcast,
direct_messages_topic_id=self._extract_direct_messages_topic_id(),
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def reply_copy(
@@ -3953,6 +4018,7 @@ class Message(MaybeInaccessibleMessage):
allow_paid_broadcast: bool | None = None,
video_start_timestamp: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
reply_to_message_id: int | None = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -4017,6 +4083,7 @@ class Message(MaybeInaccessibleMessage):
allow_paid_broadcast=allow_paid_broadcast,
direct_messages_topic_id=self._extract_direct_messages_topic_id(),
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def reply_paid_media(
+26 -4
View File
@@ -183,9 +183,17 @@ class OwnedGiftRegular(OwnedGift):
available anymore.
convert_star_count (:obj:`int`, optional): Number of Telegram Stars that can be
claimed by the receiver instead of the gift; omitted if the gift cannot be converted
to Telegram Stars.
to Telegram Stars; for gifts received on behalf of business accounts only.
prepaid_upgrade_star_count (:obj:`int`, optional): Number of Telegram Stars that were
paid by the sender for the ability to upgrade the gift.
paid for the ability to upgrade the gift.
is_upgrade_separate (:obj:`bool`, optional): :obj:`True`, if the gift's upgrade was
purchased after the gift was sent; for gifts received on behalf of business accounts
.. versionadded:: NEXT.VERSION
unique_gift_number (:obj:`int`, optional): Unique number reserved for this gift when
upgraded. See the number field in :class:`~telegram.UniqueGift`
... versionadded:: NEXT.VERSION
Attributes:
type (:obj:`str`): Type of the gift, always :attr:`~telegram.OwnedGift.REGULAR`.
@@ -208,9 +216,17 @@ class OwnedGiftRegular(OwnedGift):
available anymore.
convert_star_count (:obj:`int`): Optional. Number of Telegram Stars that can be
claimed by the receiver instead of the gift; omitted if the gift cannot be converted
to Telegram Stars.
to Telegram Stars; for gifts received on behalf of business accounts only.
prepaid_upgrade_star_count (:obj:`int`): Optional. Number of Telegram Stars that were
paid by the sender for the ability to upgrade the gift.
paid for the ability to upgrade the gift.
is_upgrade_separate (:obj:`bool`): Optional. :obj:`True`, if the gift's upgrade was
purchased after the gift was sent; for gifts received on behalf of business accounts
.. versionadded:: NEXT.VERSION
unique_gift_number (:obj:`int`): Optional. Unique number reserved for this gift when
upgraded. See the number field in :class:`~telegram.UniqueGift`
... versionadded:: NEXT.VERSION
"""
@@ -221,11 +237,13 @@ class OwnedGiftRegular(OwnedGift):
"gift",
"is_private",
"is_saved",
"is_upgrade_separate",
"owned_gift_id",
"prepaid_upgrade_star_count",
"send_date",
"sender_user",
"text",
"unique_gift_number",
"was_refunded",
)
@@ -243,6 +261,8 @@ class OwnedGiftRegular(OwnedGift):
was_refunded: bool | None = None,
convert_star_count: int | None = None,
prepaid_upgrade_star_count: int | None = None,
is_upgrade_separate: bool | None = None,
unique_gift_number: int | None = None,
*,
api_kwargs: JSONDict | None = None,
) -> None:
@@ -261,6 +281,8 @@ class OwnedGiftRegular(OwnedGift):
self.was_refunded: bool | None = was_refunded
self.convert_star_count: int | None = convert_star_count
self.prepaid_upgrade_star_count: int | None = prepaid_upgrade_star_count
self.is_upgrade_separate: bool | None = is_upgrade_separate
self.unique_gift_number: int | None = unique_gift_number
self._id_attrs = (self.type, self.gift, self.send_date)
+45 -1
View File
@@ -22,7 +22,8 @@ from typing import TYPE_CHECKING
from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput, TimePeriod
if TYPE_CHECKING:
from telegram import Bot
@@ -77,3 +78,46 @@ class Story(TelegramObject):
data["chat"] = Chat.de_json(data.get("chat", {}), bot)
return super().de_json(data=data, bot=bot)
async def repost(
self,
business_connection_id: str,
active_period: TimePeriod,
post_to_chat_page: bool | None = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> "Story":
"""Shortcut for::
await bot.repost_story(
from_chat_id=story.chat.id,
from_story_id=story.id,
*args, **kwargs
)
For the documentation of the arguments, please see :meth:`telegram.Bot.repost_story`.
.. versionadded:: NEXT.VERSION
Returns:
:class:`Story`: On success, :class:`Story` is returned.
"""
return await self.get_bot().repost_story(
business_connection_id=business_connection_id,
from_chat_id=self.chat.id,
from_story_id=self.id,
active_period=active_period,
post_to_chat_page=post_to_chat_page,
protect_content=protect_content,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
+219 -12
View File
@@ -20,6 +20,7 @@
"""This module contains classes related to unique gifs."""
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Final
from telegram import constants
@@ -27,14 +28,94 @@ from telegram._chat import Chat
from telegram._files.sticker import Sticker
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.argumentparsing import de_json_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram._utils.warnings_transition import (
build_deprecation_warning_message,
warn_about_deprecated_attr_in_property,
)
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import Bot
class UniqueGiftColors(TelegramObject):
"""This object contains information about the color scheme for a user's name, message replies
and link previews based on a unique gift.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal if their :attr:`model_custom_emoji_id`, :attr:`symbol_custom_emoji_id`,
:attr:`light_theme_main_color`, :attr:`light_theme_other_colors`,
:attr:`dark_theme_main_color`, and :attr:`dark_theme_other_colors` are equal.
.. versionadded:: NEXT.VERSION
Args:
model_custom_emoji_id (:obj:`str`): Custom emoji identifier of the unique gift's model.
symbol_custom_emoji_id (:obj:`str`): Custom emoji identifier of the unique gift's symbol.
light_theme_main_color (:obj:`int`): Main color used in light themes; RGB format.
light_theme_other_colors (Sequence[:obj:`int`]): List of 1-3 additional colors used in
light themes; RGB format. |sequenceclassargs|
dark_theme_main_color (:obj:`int`): Main color used in dark themes; RGB format.
dark_theme_other_colors (Sequence[:obj:`int`]): List of 1-3 additional colors used in dark
themes; RGB format. |sequenceclassargs|
Attributes:
model_custom_emoji_id (:obj:`str`): Custom emoji identifier of the unique gift's model.
symbol_custom_emoji_id (:obj:`str`): Custom emoji identifier of the unique gift's symbol.
light_theme_main_color (:obj:`int`): Main color used in light themes; RGB format.
light_theme_other_colors (Tuple[:obj:`int`]): Tuple of 1-3 additional colors used in
light themes; RGB format.
dark_theme_main_color (:obj:`int`): Main color used in dark themes; RGB format.
dark_theme_other_colors (Tuple[:obj:`int`]): Tuple of 1-3 additional colors used in dark
themes; RGB format.
"""
__slots__ = (
"dark_theme_main_color",
"dark_theme_other_colors",
"light_theme_main_color",
"light_theme_other_colors",
"model_custom_emoji_id",
"symbol_custom_emoji_id",
)
def __init__(
self,
model_custom_emoji_id: str,
symbol_custom_emoji_id: str,
light_theme_main_color: int,
light_theme_other_colors: Sequence[int],
dark_theme_main_color: int,
dark_theme_other_colors: Sequence[int],
*,
api_kwargs: JSONDict | None = None,
):
super().__init__(api_kwargs=api_kwargs)
self.model_custom_emoji_id: str = model_custom_emoji_id
self.symbol_custom_emoji_id: str = symbol_custom_emoji_id
self.light_theme_main_color: int = light_theme_main_color
self.light_theme_other_colors: tuple[int, ...] = parse_sequence_arg(
light_theme_other_colors
)
self.dark_theme_main_color: int = dark_theme_main_color
self.dark_theme_other_colors: tuple[int, ...] = parse_sequence_arg(dark_theme_other_colors)
self._id_attrs = (
self.model_custom_emoji_id,
self.symbol_custom_emoji_id,
self.light_theme_main_color,
self.light_theme_other_colors,
self.dark_theme_main_color,
self.dark_theme_other_colors,
)
self._freeze()
class UniqueGiftModel(TelegramObject):
"""This object describes the model of a unique gift.
@@ -260,6 +341,9 @@ class UniqueGift(TelegramObject):
.. versionadded:: 22.1
Args:
gift_id (:obj:`str`): Identifier of the regular gift from which the gift was upgraded.
.. versionadded:: NEXT.VERSION
base_name (:obj:`str`): Human-readable name of the regular gift from which this unique
gift was upgraded.
name (:obj:`str`): Unique name of the gift. This name can be used
@@ -273,8 +357,24 @@ class UniqueGift(TelegramObject):
published the gift.
.. versionadded:: 22.4
is_premium (:obj:`bool`, optional): :obj:`True`, if the original regular gift was
exclusively purchaseable by Telegram Premium subscribers.
.. versionadded:: NEXT.VERSION
is_from_blockchain (:obj:`bool`, optional): :obj:`True`, if the gift is assigned from the
TON blockchain and can't be resold or transferred in Telegram.
.. versionadded:: NEXT.VERSION
colors (:class:`telegram.UniqueGiftColors`, optional): The color scheme that can be used
by the gift's owner for the chat's name, replies to messages and link previews; for
business account gifts and gifts that are currently on sale only.
.. versionadded:: NEXT.VERSION
Attributes:
gift_id (:obj:`str`): Identifier of the regular gift from which the gift was upgraded.
.. versionadded:: NEXT.VERSION
base_name (:obj:`str`): Human-readable name of the regular gift from which this unique
gift was upgraded.
name (:obj:`str`): Unique name of the gift. This name can be used
@@ -288,12 +388,29 @@ class UniqueGift(TelegramObject):
published the gift.
.. versionadded:: 22.4
is_premium (:obj:`bool`): Optional. :obj:`True`, if the original regular gift was
exclusively purchaseable by Telegram Premium subscribers.
.. versionadded:: NEXT.VERSION
is_from_blockchain (:obj:`bool`): Optional. :obj:`True`, if the gift is assigned from the
TON blockchain and can't be resold or transferred in Telegram.
.. versionadded:: NEXT.VERSION
colors (:class:`telegram.UniqueGiftColors`): Optional. The color scheme that can be used
by the gift's owner for the chat's name, replies to messages and link previews; for
business account gifts and gifts that are currently on sale only.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
"backdrop",
"base_name",
"colors",
"gift_id",
"is_from_blockchain",
"is_premium",
"model",
"name",
"number",
@@ -310,10 +427,21 @@ class UniqueGift(TelegramObject):
symbol: UniqueGiftSymbol,
backdrop: UniqueGiftBackdrop,
publisher_chat: Chat | None = None,
# tags: deprecated NEXT.VERSION, bot api 9.3
# temporarily optional to account for changed signature
gift_id: str | None = None,
is_from_blockchain: bool | None = None,
is_premium: bool | None = None,
colors: UniqueGiftColors | None = None,
*,
api_kwargs: JSONDict | None = None,
):
# tags: deprecated NEXT.VERSION, bot api 9.3
if gift_id is None:
raise TypeError("`gift_id` is a required argument since Bot API 9.3")
super().__init__(api_kwargs=api_kwargs)
self.gift_id: str = gift_id
self.base_name: str = base_name
self.name: str = name
self.number: int = number
@@ -321,6 +449,9 @@ class UniqueGift(TelegramObject):
self.symbol: UniqueGiftSymbol = symbol
self.backdrop: UniqueGiftBackdrop = backdrop
self.publisher_chat: Chat | None = publisher_chat
self.is_from_blockchain: bool | None = is_from_blockchain
self.is_premium: bool | None = is_premium
self.colors: UniqueGiftColors | None = colors
self._id_attrs = (
self.base_name,
@@ -342,6 +473,7 @@ class UniqueGift(TelegramObject):
data["symbol"] = de_json_optional(data.get("symbol"), UniqueGiftSymbol, bot)
data["backdrop"] = de_json_optional(data.get("backdrop"), UniqueGiftBackdrop, bot)
data["publisher_chat"] = de_json_optional(data.get("publisher_chat"), Chat, bot)
data["colors"] = de_json_optional(data.get("colors"), UniqueGiftColors, bot)
return super().de_json(data=data, bot=bot)
@@ -358,10 +490,14 @@ class UniqueGiftInfo(TelegramObject):
gift (:class:`UniqueGift`): Information about the gift.
origin (:obj:`str`): Origin of the gift. Currently, either :attr:`UPGRADE` for gifts
upgraded from regular gifts, :attr:`TRANSFER` for gifts transferred from other users
or channels, or :attr:`RESALE` for gifts bought from other users.
or channels, :attr:`RESALE` for gifts bought from other users,
:attr:`GIFTED_UPGRADE` for upgrades purchased after the gift was sent, or :attr:`OFFER`
for gifts bought or sold through gift purchase offers
.. versionchanged:: 22.3
The :attr:`RESALE` origin was added.
.. versionchanged:: NEXT.VERSION
Bot API 9.3 added the :attr:`GIFTED_UPGRADE` and :attr:`OFFER` origins.
owned_gift_id (:obj:`str`, optional) Unique identifier of the received gift for the
bot; only present for gifts received on behalf of business accounts.
transfer_star_count (:obj:`int`, optional): Number of Telegram Stars that must be paid
@@ -370,6 +506,18 @@ class UniqueGiftInfo(TelegramObject):
paid for the gift.
.. versionadded:: 22.3
.. deprecated:: NEXT.VERSION
Bot API 9.3 deprecated this field. Use :attr:`last_resale_currency` and
:attr:`last_resale_amount` instead.
last_resale_currency (:obj:`str`, optional): For gifts bought from other users, the
currency in which the payment for the gift was done. Currently, one of ``XTR`` for
Telegram Stars or ``TON`` for toncoins.
.. versionadded:: NEXT.VERSION
last_resale_amount (:obj:`int`, optional): For gifts bought from other users, the price
paid for the gift in either Telegram Stars or nanotoncoins.
.. versionadded:: NEXT.VERSION
next_transfer_date (:obj:`datetime.datetime`, optional): Date when the gift can be
transferred. If it's in the past, then the gift can be transferred now.
|datetime_localization|
@@ -380,18 +528,27 @@ class UniqueGiftInfo(TelegramObject):
gift (:class:`UniqueGift`): Information about the gift.
origin (:obj:`str`): Origin of the gift. Currently, either :attr:`UPGRADE` for gifts
upgraded from regular gifts, :attr:`TRANSFER` for gifts transferred from other users
or channels, or :attr:`RESALE` for gifts bought from other users.
or channels, :attr:`RESALE` for gifts bought from other users,
:attr:`GIFTED_UPGRADE` for upgrades purchased after the gift was sent, or :attr:`OFFER`
for gifts bought or sold through gift purchase offers
.. versionchanged:: 22.3
The :attr:`RESALE` origin was added.
.. versionchanged:: NEXT.VERSION
Bot API 9.3 added the :attr:`GIFTED_UPGRADE` and :attr:`OFFER` origins.
owned_gift_id (:obj:`str`) Optional. Unique identifier of the received gift for the
bot; only present for gifts received on behalf of business accounts.
transfer_star_count (:obj:`int`): Optional. Number of Telegram Stars that must be paid
to transfer the gift; omitted if the bot cannot transfer the gift.
last_resale_star_count (:obj:`int`): Optional. For gifts bought from other users, the price
paid for the gift.
last_resale_currency (:obj:`str`): Optional. For gifts bought from other users, the
currency in which the payment for the gift was done. Currently, one of ``XTR`` for
Telegram Stars or ``TON`` for toncoins.
.. versionadded:: 22.3
.. versionadded:: NEXT.VERSION
last_resale_amount (:obj:`int`): Optional. For gifts bought from other users, the price
paid for the gift in either Telegram Stars or nanotoncoins.
.. versionadded:: NEXT.VERSION
next_transfer_date (:obj:`datetime.datetime`): Optional. Date when the gift can be
transferred. If it's in the past, then the gift can be transferred now.
|datetime_localization|
@@ -399,19 +556,31 @@ class UniqueGiftInfo(TelegramObject):
.. versionadded:: 22.3
"""
UPGRADE: Final[str] = constants.UniqueGiftInfoOrigin.UPGRADE
""":const:`telegram.constants.UniqueGiftInfoOrigin.UPGRADE`"""
TRANSFER: Final[str] = constants.UniqueGiftInfoOrigin.TRANSFER
""":const:`telegram.constants.UniqueGiftInfoOrigin.TRANSFER`"""
GIFTED_UPGRADE: Final[str] = constants.UniqueGiftInfoOrigin.GIFTED_UPGRADE
""":const:`telegram.constants.UniqueGiftInfoOrigin.GIFTED_UPGRADE`
.. versionadded:: NEXT.VERSION
"""
OFFER: Final[str] = constants.UniqueGiftInfoOrigin.OFFER
""":const:`telegram.constants.UniqueGiftInfoOrigin.OFFER`
.. versionadded:: NEXT.VERSION
"""
RESALE: Final[str] = constants.UniqueGiftInfoOrigin.RESALE
""":const:`telegram.constants.UniqueGiftInfoOrigin.RESALE`
.. versionadded:: 22.3
"""
TRANSFER: Final[str] = constants.UniqueGiftInfoOrigin.TRANSFER
""":const:`telegram.constants.UniqueGiftInfoOrigin.TRANSFER`"""
UPGRADE: Final[str] = constants.UniqueGiftInfoOrigin.UPGRADE
""":const:`telegram.constants.UniqueGiftInfoOrigin.UPGRADE`"""
__slots__ = (
"_last_resale_star_count",
"gift",
"last_resale_star_count",
"last_resale_amount",
"last_resale_currency",
"next_transfer_date",
"origin",
"owned_gift_id",
@@ -424,11 +593,28 @@ class UniqueGiftInfo(TelegramObject):
origin: str,
owned_gift_id: str | None = None,
transfer_star_count: int | None = None,
# tags: deprecated NEXT.VERSION; bot api 9.3
last_resale_star_count: int | None = None,
next_transfer_date: dtm.datetime | None = None,
last_resale_currency: str | None = None,
last_resale_amount: int | None = None,
*,
api_kwargs: JSONDict | None = None,
):
if last_resale_star_count is not None:
warn(
PTBDeprecationWarning(
version="NEXT.VERSION",
message=build_deprecation_warning_message(
deprecated_name="last_resale_star_count",
new_name="last_resale_currency/amount",
bot_api_version="9.3",
object_type="parameter",
),
),
stacklevel=2,
)
super().__init__(api_kwargs=api_kwargs)
# Required
self.gift: UniqueGift = gift
@@ -436,13 +622,34 @@ class UniqueGiftInfo(TelegramObject):
# Optional
self.owned_gift_id: str | None = owned_gift_id
self.transfer_star_count: int | None = transfer_star_count
self.last_resale_star_count: int | None = last_resale_star_count
self._last_resale_star_count: int | None = last_resale_star_count
self.next_transfer_date: dtm.datetime | None = next_transfer_date
self.last_resale_currency: str | None = last_resale_currency
self.last_resale_amount: int | None = last_resale_amount
self._id_attrs = (self.gift, self.origin)
self._freeze()
# tags: deprecated NEXT.VERSION; bot api 9.3
@property
def last_resale_star_count(self) -> int | None:
""":obj:`int`: Optional. For gifts bought from other users, the price
paid for the gift.
.. versionadded:: 22.3
.. deprecated:: NEXT.VERSION
Bot API 9.3 deprecated this field. Use :attr:`last_resale_currency` and
:attr:`last_resale_amount` instead.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="last_resale_star_count",
new_attr_name="last_resale_currency/amount",
bot_api_version="9.3",
ptb_version="NEXT.VERSION",
)
return self._last_resale_star_count
@classmethod
def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UniqueGiftInfo":
"""See :meth:`telegram.TelegramObject.de_json`."""
+153
View File
@@ -56,9 +56,11 @@ if TYPE_CHECKING:
Message,
MessageEntity,
MessageId,
OwnedGifts,
PhotoSize,
ReplyParameters,
Sticker,
Story,
SuggestedPostParameters,
UserChatBoosts,
UserProfilePhotos,
@@ -112,6 +114,10 @@ class User(TelegramObject):
Returned only in :meth:`telegram.Bot.get_me`.
.. versionadded:: 21.5
has_topics_enabled (:obj:`bool`, optional): :obj:`True`, if the bot has forum topic mode
enabled in private chats. Returned only in :meth:`telegram.Bot.get_me`.
.. versionadded:: NEXT.VERSION
Attributes:
id (:obj:`int`): Unique identifier for this user or bot.
@@ -143,6 +149,10 @@ class User(TelegramObject):
Returned only in :meth:`telegram.Bot.get_me`.
.. versionadded:: 21.5
has_topics_enabled (:obj:`bool`): Optional. :obj:`True`, if the bot has forum topic mode
enabled in private chats. Returned only in :meth:`telegram.Bot.get_me`.
.. versionadded:: NEXT.VERSION
.. |user_chat_id_note| replace:: This shortcuts build on the assumption that :attr:`User.id`
coincides with the :attr:`Chat.id` of the private chat with the user. This has been the
@@ -156,6 +166,7 @@ class User(TelegramObject):
"can_read_all_group_messages",
"first_name",
"has_main_web_app",
"has_topics_enabled",
"id",
"is_bot",
"is_premium",
@@ -180,6 +191,7 @@ class User(TelegramObject):
added_to_attachment_menu: bool | None = None,
can_connect_to_business: bool | None = None,
has_main_web_app: bool | None = None,
has_topics_enabled: bool | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -199,6 +211,7 @@ class User(TelegramObject):
self.added_to_attachment_menu: bool | None = added_to_attachment_menu
self.can_connect_to_business: bool | None = can_connect_to_business
self.has_main_web_app: bool | None = has_main_web_app
self.has_topics_enabled: bool | None = has_topics_enabled
self._id_attrs = (self.id,)
@@ -486,6 +499,49 @@ class User(TelegramObject):
suggested_post_parameters=suggested_post_parameters,
)
async def send_message_draft(
self,
draft_id: int,
text: str,
message_thread_id: int | None = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
entities: Sequence["MessageEntity"] | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> bool:
"""Shortcut for::
await bot.send_message_draft(update.effective_user.id, *args, **kwargs)
For the documentation of the arguments, please see :meth:`telegram.Bot.send_message_draft`.
Note:
|user_chat_id_note|
.. versionadded:: NEXT.VERSION
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().send_message_draft(
chat_id=self.id,
draft_id=draft_id,
text=text,
message_thread_id=message_thread_id,
parse_mode=parse_mode,
entities=entities,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def delete_message(
self,
message_id: int,
@@ -1812,6 +1868,7 @@ class User(TelegramObject):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
reply_to_message_id: int | None = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1858,6 +1915,7 @@ class User(TelegramObject):
allow_paid_broadcast=allow_paid_broadcast,
direct_messages_topic_id=direct_messages_topic_id,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def copy_message(
@@ -1877,6 +1935,7 @@ class User(TelegramObject):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
reply_to_message_id: int | None = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1923,6 +1982,7 @@ class User(TelegramObject):
allow_paid_broadcast=allow_paid_broadcast,
direct_messages_topic_id=direct_messages_topic_id,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def send_copies(
@@ -2029,6 +2089,7 @@ class User(TelegramObject):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2065,6 +2126,7 @@ class User(TelegramObject):
message_thread_id=message_thread_id,
direct_messages_topic_id=direct_messages_topic_id,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def forward_to(
@@ -2077,6 +2139,7 @@ class User(TelegramObject):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2114,6 +2177,7 @@ class User(TelegramObject):
message_thread_id=message_thread_id,
direct_messages_topic_id=direct_messages_topic_id,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def forward_messages_from(
@@ -2475,3 +2539,92 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def repost_story(
self,
business_connection_id: str,
from_story_id: int,
active_period: TimePeriod,
post_to_chat_page: bool | None = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> "Story":
"""Shortcut for::
await bot.repost_story(
from_chat_id=update.effective_user.id,
*args, **kwargs
)
For the documentation of the arguments, please see :meth:`telegram.Bot.repost_story`.
.. versionadded:: NEXT.VERSION
Returns:
:class:`Story`: On success, :class:`Story` is returned.
"""
return await self.get_bot().repost_story(
business_connection_id=business_connection_id,
from_chat_id=self.id,
from_story_id=from_story_id,
active_period=active_period,
post_to_chat_page=post_to_chat_page,
protect_content=protect_content,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def get_gifts(
self,
exclude_unlimited: bool | None = None,
exclude_limited_upgradable: bool | None = None,
exclude_limited_non_upgradable: bool | None = None,
exclude_from_blockchain: bool | None = None,
exclude_unique: bool | None = None,
sort_by_price: bool | None = None,
offset: str | None = None,
limit: int | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> "OwnedGifts":
"""Shortcut for::
await bot.get_user_gifts(user_id=update.effective_user.id)
For the documentation of the arguments, please see
:meth:`telegram.Bot.get_user_gifts`.
.. versionadded:: NEXT.VERSION
Returns:
:class:`telegram.OwnedGifts`: On success, returns the gifts owned by the user.
"""
return await self.get_bot().get_user_gifts(
user_id=self.id,
exclude_unlimited=exclude_unlimited,
exclude_limited_upgradable=exclude_limited_upgradable,
exclude_limited_non_upgradable=exclude_limited_non_upgradable,
exclude_from_blockchain=exclude_from_blockchain,
exclude_unique=exclude_unique,
sort_by_price=sort_by_price,
offset=offset,
limit=limit,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2026
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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/].
"""This module contains an object that represents a Telegram user rating."""
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class UserRating(TelegramObject):
"""
This object describes the rating of a user based on their Telegram Star spendings.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`level` and :attr:`rating` are equal.
.. versionadded:: NEXT.VERSION
Args:
level (:obj:`int`): Current level of the user, indicating their reliability when purchasing
digital goods and services. A higher level suggests a more trustworthy customer; a
negative level is likely reason for concern.
rating (:obj:`int`): Numerical value of the user's rating; the higher the rating, the
better
current_level_rating (:obj:`int`): The rating value required to get the current level
next_level_rating (:obj:`int`, optional): The rating value required to get to the next
level; omitted if the maximum level was reached
Attributes:
level (:obj:`int`): Current level of the user, indicating their reliability when purchasing
digital goods and services. A higher level suggests a more trustworthy customer; a
negative level is likely reason for concern.
rating (:obj:`int`): Numerical value of the user's rating; the higher the rating, the
better
current_level_rating (:obj:`int`): The rating value required to get the current level
next_level_rating (:obj:`int`): Optional. The rating value required to get to the next
level; omitted if the maximum level was reached
"""
__slots__ = ("current_level_rating", "level", "next_level_rating", "rating")
def __init__(
self,
level: int,
rating: int,
current_level_rating: int,
next_level_rating: int | None = None,
*,
api_kwargs: JSONDict | None = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.level: int = level
self.rating: int = rating
self.current_level_rating: int = current_level_rating
self.next_level_rating: int | None = next_level_rating
self._id_attrs = (self.level, self.rating)
self._freeze()
+56 -24
View File
@@ -176,7 +176,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=2)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=3)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -761,13 +761,23 @@ class BusinessLimit(IntEnum):
"""
MIN_GIFT_RESULTS = 1
""":obj:`int`: Minimum number of gifts to be returned. Relevant for
:paramref:`~telegram.Bot.get_business_account_gifts.limit` of
:meth:`telegram.Bot.get_business_account_gifts`.
* :paramref:`~telegram.Bot.get_business_account_gifts.limit` of
:meth:`telegram.Bot.get_business_account_gifts`.
* :paramref:`~telegram.Bot.get_chat_gifts.limit` of
:meth:`telegram.Bot.get_chat_gifts`.
* :paramref:`~telegram.Bot.get_user_gifts.limit` of
:meth:`telegram.Bot.get_user_gifts`.
"""
MAX_GIFT_RESULTS = 100
""":obj:`int`: Maximum number of gifts to be returned. Relevant for
:paramref:`~telegram.Bot.get_business_account_gifts.limit` of
:meth:`telegram.Bot.get_business_account_gifts`.
* :paramref:`~telegram.Bot.get_business_account_gifts.limit` of
:meth:`telegram.Bot.get_business_account_gifts`.
* :paramref:`~telegram.Bot.get_chat_gifts.limit` of
:meth:`telegram.Bot.get_chat_gifts`.
* :paramref:`~telegram.Bot.get_user_gifts.limit` of
:meth:`telegram.Bot.get_user_gifts`.
"""
MIN_STAR_COUNT = 1
""":obj:`int`: Minimum number of Telegram Stars to be transfered. Relevant for
@@ -2010,6 +2020,8 @@ class MessageLimit(IntEnum):
* :paramref:`~telegram.Bot.send_message.text` parameter of :meth:`telegram.Bot.send_message`
* :paramref:`~telegram.Bot.edit_message_text.text` parameter of
:meth:`telegram.Bot.edit_message_text`
* :paramref:`~telegram.Bot.send_message_draft.text` parameter of
:meth:`telegram.Bot.send_message_draft`
"""
CAPTION_LENGTH = 1024
""":obj:`int`: Maximum number of characters in a :obj:`str` passed as:
@@ -2025,11 +2037,14 @@ class MessageLimit(IntEnum):
"""
# constants above this line are tested
MIN_TEXT_LENGTH = 1
""":obj:`int`: Minimum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.InputTextMessageContent.message_text` parameter of
:class:`telegram.InputTextMessageContent` and the
:paramref:`~telegram.Bot.edit_message_text.text` parameter of
:meth:`telegram.Bot.edit_message_text`.
""":obj:`int`: Minimum number of characters in a :obj:`str` passed as:
* :paramref:`~telegram.InputTextMessageContent.message_text` parameter of
:class:`telegram.InputTextMessageContent`.
* :paramref:`~telegram.Bot.edit_message_text.text` parameter of
:meth:`telegram.Bot.edit_message_text`.
* :paramref:`~telegram.Bot.send_message_draft.text` parameter of
:meth:`telegram.Bot.send_message_draft`.
"""
DEEP_LINK_LENGTH = 64
""":obj:`int`: Maximum number of characters for a deep link."""
@@ -2175,6 +2190,11 @@ class MessageType(StringEnum):
.. versionadded:: 22.1
"""
GIFT_UPGRADE_SENT = "gift_upgrade_sent"
""":obj:`str`: Messages with :attr:`telegram.Message.gift_upgrade_sent`.
.. versionadded:: NEXT.VERSION
"""
GIVEAWAY = "giveaway"
""":obj:`str`: Messages with :attr:`telegram.Message.giveaway`.
@@ -3058,7 +3078,7 @@ class StoryAreaTypeType(StringEnum):
""":obj:`str`: Type of :class:`telegram.StoryAreaTypeUniqueGift`."""
class StoryLimit(StringEnum):
class StoryLimit(IntEnum):
"""This enum contains limitations for :meth:`~telegram.Bot.post_story` and
:meth:`~telegram.Bot.edit_story`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
@@ -3074,17 +3094,17 @@ class StoryLimit(StringEnum):
:meth:`telegram.Bot.edit_story`.
"""
ACTIVITY_SIX_HOURS = 6 * 3600
""":obj:`int`: Possible value for :paramref:`~telegram.Bot.post_story.caption`` parameter of
:meth:`telegram.Bot.post_story`."""
""":obj:`int`: Possible value for :paramref:`~telegram.Bot.post_story.active_period`` parameter
of :meth:`telegram.Bot.post_story`."""
ACTIVITY_TWELVE_HOURS = 12 * 3600
""":obj:`int`: Possible value for :paramref:`~telegram.Bot.post_story.caption`` parameter of
:meth:`telegram.Bot.post_story`."""
""":obj:`int`: Possible value for :paramref:`~telegram.Bot.post_story.active_period`` parameter
of :meth:`telegram.Bot.post_story`."""
ACTIVITY_ONE_DAY = 86400
""":obj:`int`: Possible value for :paramref:`~telegram.Bot.post_story.caption`` parameter of
:meth:`telegram.Bot.post_story`."""
""":obj:`int`: Possible value for :paramref:`~telegram.Bot.post_story.active_period`` parameter
of :meth:`telegram.Bot.post_story`."""
ACTIVITY_TWO_DAYS = 2 * 86400
""":obj:`int`: Possible value for :paramref:`~telegram.Bot.post_story.caption`` parameter of
:meth:`telegram.Bot.post_story`."""
""":obj:`int`: Possible value for :paramref:`~telegram.Bot.post_story.active_period`` parameter
of :meth:`telegram.Bot.post_story`."""
class SuggestedPost(IntEnum):
@@ -3328,15 +3348,25 @@ class UniqueGiftInfoOrigin(StringEnum):
__slots__ = ()
UPGRADE = "upgrade"
""":obj:`str` gift upgraded"""
TRANSFER = "transfer"
""":obj:`str` gift transfered"""
GIFTED_UPGRADE = "gifted_upgrade"
""":obj:`str` upgrades purchased after the gift was sent
.. versionadded:: NEXT.VERSION
"""
OFFER = "OFFER"
""":obj:`str` gift bought or sold through gift purchase offers
.. versionadded:: NEXT.VERSION
"""
RESALE = "resale"
""":obj:`str` gift bought from other users
.. versionadded:: 22.3
"""
TRANSFER = "transfer"
""":obj:`str` gift transfered"""
UPGRADE = "upgrade"
""":obj:`str` gift upgraded"""
class UpdateType(StringEnum):
@@ -3510,7 +3540,7 @@ class InvoiceLimit(IntEnum):
.. versionadded:: 21.6
"""
MAX_STAR_COUNT = 10000
MAX_STAR_COUNT = 25000
""":obj:`int`: Maximum amount of starts that must be paid to buy access to a paid media
passed as :paramref:`~telegram.Bot.send_paid_media.star_count` parameter of
:meth:`telegram.Bot.send_paid_media`.
@@ -3518,6 +3548,8 @@ class InvoiceLimit(IntEnum):
.. versionadded:: 21.6
.. versionchanged:: 22.1
Bot API 9.0 changed the value to 10000.
.. versionchanged:: NEXT.VERSION
Bot API 9.3 changed the value to 25000.
"""
SUBSCRIPTION_PERIOD = dtm.timedelta(days=30).total_seconds()
""":obj:`int`: The period of time for which the subscription is active before
+150
View File
@@ -831,6 +831,7 @@ class ExtBot(Bot, Generic[RLARGS]):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
reply_to_message_id: int | None = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -866,6 +867,7 @@ class ExtBot(Bot, Generic[RLARGS]):
allow_paid_broadcast=allow_paid_broadcast,
direct_messages_topic_id=direct_messages_topic_id,
suggested_post_parameters=suggested_post_parameters,
message_effect_id=message_effect_id,
)
async def copy_messages(
@@ -1776,6 +1778,7 @@ class ExtBot(Bot, Generic[RLARGS]):
video_start_timestamp: int | None = None,
direct_messages_topic_id: int | None = None,
suggested_post_parameters: "SuggestedPostParameters | None" = None,
message_effect_id: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1799,6 +1802,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
direct_messages_topic_id=direct_messages_topic_id,
message_effect_id=message_effect_id,
)
async def forward_messages(
@@ -3134,6 +3138,36 @@ class ExtBot(Bot, Generic[RLARGS]):
suggested_post_parameters=suggested_post_parameters,
)
async def send_message_draft(
self,
chat_id: int,
draft_id: int,
text: str,
message_thread_id: int | None = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
entities: Sequence["MessageEntity"] | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
rate_limit_args: RLARGS | None = None,
) -> bool:
return await super().send_message_draft(
chat_id=chat_id,
draft_id=draft_id,
text=text,
message_thread_id=message_thread_id,
parse_mode=parse_mode,
entities=entities,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def send_photo(
self,
chat_id: int | str,
@@ -4463,6 +4497,9 @@ class ExtBot(Bot, Generic[RLARGS]):
sort_by_price: bool | None = None,
offset: str | None = None,
limit: int | None = None,
exclude_limited_upgradable: bool | None = None,
exclude_limited_non_upgradable: bool | None = None,
exclude_from_blockchain: bool | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -4477,7 +4514,10 @@ class ExtBot(Bot, Generic[RLARGS]):
exclude_saved=exclude_saved,
exclude_unlimited=exclude_unlimited,
exclude_limited=exclude_limited,
exclude_limited_upgradable=exclude_limited_upgradable,
exclude_limited_non_upgradable=exclude_limited_non_upgradable,
exclude_unique=exclude_unique,
exclude_from_blockchain=exclude_from_blockchain,
sort_by_price=sort_by_price,
offset=offset,
limit=limit,
@@ -5278,9 +5318,116 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def repost_story(
self,
business_connection_id: str,
from_chat_id: int,
from_story_id: int,
active_period: TimePeriod,
post_to_chat_page: bool | None = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
rate_limit_args: RLARGS | None = None,
) -> Story:
return await super().repost_story(
business_connection_id=business_connection_id,
from_chat_id=from_chat_id,
from_story_id=from_story_id,
active_period=active_period,
post_to_chat_page=post_to_chat_page,
protect_content=protect_content,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_user_gifts(
self,
user_id: int,
exclude_unlimited: bool | None = None,
exclude_limited_upgradable: bool | None = None,
exclude_limited_non_upgradable: bool | None = None,
exclude_from_blockchain: bool | None = None,
exclude_unique: bool | None = None,
sort_by_price: bool | None = None,
offset: str | None = None,
limit: int | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
rate_limit_args: RLARGS | None = None,
) -> OwnedGifts:
return await super().get_user_gifts(
user_id=user_id,
exclude_unlimited=exclude_unlimited,
exclude_limited_upgradable=exclude_limited_upgradable,
exclude_limited_non_upgradable=exclude_limited_non_upgradable,
exclude_from_blockchain=exclude_from_blockchain,
exclude_unique=exclude_unique,
sort_by_price=sort_by_price,
offset=offset,
limit=limit,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_chat_gifts(
self,
chat_id: int | str,
exclude_unsaved: bool | None = None,
exclude_saved: bool | None = None,
exclude_unlimited: bool | None = None,
exclude_limited_upgradable: bool | None = None,
exclude_limited_non_upgradable: bool | None = None,
exclude_from_blockchain: bool | None = None,
exclude_unique: bool | None = None,
sort_by_price: bool | None = None,
offset: str | None = None,
limit: int | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
rate_limit_args: RLARGS | None = None,
) -> OwnedGifts:
return await super().get_chat_gifts(
chat_id=chat_id,
exclude_unsaved=exclude_unsaved,
exclude_saved=exclude_saved,
exclude_unlimited=exclude_unlimited,
exclude_limited_upgradable=exclude_limited_upgradable,
exclude_limited_non_upgradable=exclude_limited_non_upgradable,
exclude_from_blockchain=exclude_from_blockchain,
exclude_unique=exclude_unique,
sort_by_price=sort_by_price,
offset=offset,
limit=limit,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
# updated camelCase aliases
getMe = get_me
sendMessage = send_message
sendMessageDraft = send_message_draft
deleteMessage = delete_message
deleteMessages = delete_messages
forwardMessage = forward_message
@@ -5436,3 +5583,6 @@ class ExtBot(Bot, Generic[RLARGS]):
getMyStarBalance = get_my_star_balance
approveSuggestedPost = approve_suggested_post
declineSuggestedPost = decline_suggested_post
repostStory = repost_story
getUserGifts = get_user_gifts
getChatGifts = get_chat_gifts
+13
View File
@@ -1978,6 +1978,7 @@ class StatusUpdate:
or StatusUpdate.GENERAL_FORUM_TOPIC_HIDDEN.check_update(update)
or StatusUpdate.GENERAL_FORUM_TOPIC_UNHIDDEN.check_update(update)
or StatusUpdate.GIFT.check_update(update)
or StatusUpdate.GIFT_UPGRADE_SENT.check_update(update)
or StatusUpdate.GIVEAWAY_COMPLETED.check_update(update)
or StatusUpdate.GIVEAWAY_CREATED.check_update(update)
or StatusUpdate.LEFT_CHAT_MEMBER.check_update(update)
@@ -2188,6 +2189,18 @@ class StatusUpdate:
.. versionadded:: 22.1
"""
class _GiftUpgradeSent(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.gift_upgrade_sent)
GIFT_UPGRADE_SENT = _GiftUpgradeSent(name="filters.StatusUpdate.GIFT_UPGRADE_SENT")
"""Messages that contain :attr:`telegram.Message.gift_upgrade_sent`.
.. versionadded:: NEXT.VERSION
"""
class _GiveawayCreated(MessageFilter):
__slots__ = ()
+5 -1
View File
@@ -81,7 +81,11 @@ _PREPARED_DUMMY_OBJECTS: dict[str, object] = {
accent_color_id=1,
max_reaction_count=1,
accepted_gift_types=AcceptedGiftTypes(
unlimited_gifts=True, limited_gifts=True, unique_gifts=True, premium_subscription=True
unlimited_gifts=True,
limited_gifts=True,
unique_gifts=True,
premium_subscription=True,
gifts_from_channels=True,
),
),
"ChatInviteLink": ChatInviteLink(
+5
View File
@@ -1136,6 +1136,11 @@ class TestFilters:
assert filters.StatusUpdate.UNIQUE_GIFT.check_update(update)
update.message.unique_gift = None
update.message.gift_upgrade_sent = "gift_upgrade_sent"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.GIFT_UPGRADE_SENT.check_update(update)
update.message.gift_upgrade_sent = None
update.message.paid_message_price_changed = "paid_message_price_changed"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.PAID_MESSAGE_PRICE_CHANGED.check_update(update)
+132
View File
@@ -65,6 +65,7 @@ from telegram import (
MenuButtonWebApp,
Message,
MessageEntity,
OwnedGifts,
Poll,
PollOption,
PreparedInlineMessage,
@@ -1477,6 +1478,61 @@ class TestBotWithoutRequest:
"SoSecretToken",
)
async def test_send_message_draft(self, offline_bot, monkeypatch):
entities = [
MessageEntity(MessageEntity.BOLD, 0, 3),
MessageEntity(MessageEntity.ITALIC, 5, 8),
]
async def make_assertions(*args, **kwargs):
params = kwargs.get("request_data").parameters
assert params.get("chat_id") == 123
assert params.get("draft_id") == 1
assert params.get("text") == "test test"
assert params.get("message_thread_id") == 9
assert params.get("parse_mode") == "markdown"
assert params.get("entities") == [e.to_dict() for e in entities]
return True
monkeypatch.setattr(offline_bot.request, "post", make_assertions)
assert await offline_bot.send_message_draft(
chat_id=123,
draft_id=1,
text="test test",
message_thread_id=9,
parse_mode="markdown",
entities=entities,
)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
@pytest.mark.parametrize(
("passed_value", "expected_value"),
[(DEFAULT_NONE, "Markdown"), ("HTML", "HTML"), (None, None)],
)
async def test_send_message_draft_default_parse_mode(
self, default_bot, monkeypatch, passed_value, expected_value
):
async def make_assertion(url, request_data, *args, **kwargs):
assert request_data.parameters.get("parse_mode") == expected_value
return True
monkeypatch.setattr(default_bot.request, "post", make_assertion)
kwargs = {
"chat_id": 123,
"draft_id": 1,
"text": "test test",
"message_thread_id": 9,
"entities": [
MessageEntity(MessageEntity.BOLD, 0, 3),
MessageEntity(MessageEntity.ITALIC, 5, 8),
],
}
if passed_value is not DEFAULT_NONE:
kwargs["parse_mode"] = passed_value
await default_bot.send_message_draft(**kwargs)
# TODO: Needs improvement. Need incoming shipping queries to test
async def test_answer_shipping_query_ok(self, monkeypatch, offline_bot):
# For now just test that our internals pass the correct data
@@ -2712,6 +2768,72 @@ class TestBotWithoutRequest:
await offline_bot.decline_suggested_post(1234, 5678, "declined")
async def test_get_user_gifts_parameter_passing(self, offline_bot, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
for param in (
"user_id",
"exclude_unlimited",
"exclude_limited_upgradable",
"exclude_limited_non_upgradable",
"exclude_from_blockchain",
"exclude_unique",
"sort_by_price",
"offset",
"limit",
):
assert request_data.parameters.get(param) == param
return OwnedGifts(0, [], "null").to_dict()
monkeypatch.setattr(offline_bot.request, "post", make_assertion)
await offline_bot.get_user_gifts(
user_id="user_id",
exclude_unlimited="exclude_unlimited",
exclude_limited_upgradable="exclude_limited_upgradable",
exclude_limited_non_upgradable="exclude_limited_non_upgradable",
exclude_from_blockchain="exclude_from_blockchain",
exclude_unique="exclude_unique",
sort_by_price="sort_by_price",
offset="offset",
limit="limit",
)
async def test_get_chat_gifts_parameter_passing(self, offline_bot, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
for param in (
"chat_id",
"exclude_saved",
"exclude_unsaved",
"exclude_unlimited",
"exclude_limited_upgradable",
"exclude_limited_non_upgradable",
"exclude_from_blockchain",
"exclude_unique",
"sort_by_price",
"offset",
"limit",
):
assert request_data.parameters.get(param) == param
return OwnedGifts(0, [], "null").to_dict()
monkeypatch.setattr(offline_bot.request, "post", make_assertion)
await offline_bot.get_chat_gifts(
chat_id="chat_id",
exclude_saved="exclude_saved",
exclude_unsaved="exclude_unsaved",
exclude_unlimited="exclude_unlimited",
exclude_limited_upgradable="exclude_limited_upgradable",
exclude_limited_non_upgradable="exclude_limited_non_upgradable",
exclude_from_blockchain="exclude_from_blockchain",
exclude_unique="exclude_unique",
sort_by_price="sort_by_price",
offset="offset",
limit="limit",
)
class TestBotWithRequest:
"""
@@ -4687,6 +4809,16 @@ class TestBotWithRequest:
assert isinstance(balance, StarAmount)
assert balance.amount == 0
async def test_get_user_gifts_basic(self, bot):
gifts = await bot.get_user_gifts(bot.bot.id)
assert isinstance(gifts, OwnedGifts)
assert gifts.total_count == 0
async def test_get_chat_gifts_basic(self, bot, chat_id):
gifts = await bot.get_chat_gifts(chat_id)
assert isinstance(gifts, OwnedGifts)
assert gifts.total_count == 0
async def test_initialize_tracks_requests_and_bot_separately(self, offline_bot, monkeypatch):
"""Test that requests and bot user are initialized separately and only once."""
request_init_count = 0
+97 -1
View File
@@ -21,6 +21,7 @@ import datetime as dtm
import pytest
from telegram import (
Bot,
BusinessBotRights,
BusinessConnection,
Chat,
@@ -45,7 +46,10 @@ from telegram._reply import ReplyParameters
from telegram._utils.datetime import UTC
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram.constants import InputProfilePhotoType, InputStoryContentType
from telegram.ext import ExtBot
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.files import data_file
from tests.auxil.networking import OfflineRequest
class BusinessMethodsTestBase:
@@ -107,7 +111,10 @@ class TestBusinessMethodsWithoutRequest(BusinessMethodsTestBase):
assert data.get("exclude_saved") is bool_param
assert data.get("exclude_unlimited") is bool_param
assert data.get("exclude_limited") is bool_param
assert data.get("exclude_limited_upgradable") is bool_param
assert data.get("exclude_limited_non_upgradable") is bool_param
assert data.get("exclude_unique") is bool_param
assert data.get("exclude_from_blockchain") is bool_param
assert data.get("sort_by_price") is bool_param
assert data.get("offset") == offset
assert data.get("limit") == limit
@@ -121,13 +128,50 @@ class TestBusinessMethodsWithoutRequest(BusinessMethodsTestBase):
exclude_saved=bool_param,
exclude_unlimited=bool_param,
exclude_limited=bool_param,
exclude_limited_upgradable=bool_param,
exclude_limited_non_upgradable=bool_param,
exclude_unique=bool_param,
exclude_from_blockchain=bool_param,
sort_by_price=bool_param,
offset=offset,
limit=limit,
)
assert isinstance(obj, OwnedGifts)
@pytest.mark.parametrize("bot_class", [Bot, ExtBot])
async def test_get_business_account_gifts_exclude_limited_deprecation(
self, offline_bot, monkeypatch, bot_class
):
bot = bot_class(offline_bot.token, request=OfflineRequest())
async def dummy_response(*args, **kwargs):
return OwnedGifts(
total_count=1,
gifts=[
OwnedGiftRegular(
gift=Gift(
id="id1",
sticker=Sticker(
"file_id", "file_unique_id", 512, 512, False, False, "regular"
),
star_count=5,
),
send_date=dtm.datetime.now(tz=UTC).replace(microsecond=0),
owned_gift_id="some_id_1",
)
],
).to_dict()
monkeypatch.setattr(bot.request, "post", dummy_response)
with pytest.warns(PTBDeprecationWarning, match=r"9\.3.*exclude_limited") as record:
await bot.get_business_account_gifts(
business_connection_id=self.bci,
exclude_limited=True,
)
assert record[0].category == PTBDeprecationWarning
assert record[0].filename == __file__, "wrong stacklevel!"
async def test_get_business_account_star_balance(self, offline_bot, monkeypatch):
star_amount_json = StarAmount(amount=100, nanostar_amount=356).to_json()
@@ -214,7 +258,7 @@ class TestBusinessMethodsWithoutRequest(BusinessMethodsTestBase):
async def test_set_business_account_gift_settings(self, offline_bot, monkeypatch):
show_gift_button = True
accepted_gift_types = AcceptedGiftTypes(True, True, True, True)
accepted_gift_types = AcceptedGiftTypes(True, True, True, True, True)
async def make_assertion(*args, **kwargs):
data = kwargs.get("request_data").json_parameters
@@ -789,3 +833,55 @@ class TestBusinessMethodsWithoutRequest(BusinessMethodsTestBase):
reply_markup=reply_markup,
)
assert isinstance(obj, Message)
async def test_repost_story(self, offline_bot, monkeypatch):
"""No way to test this without stories"""
async def make_assertion(url, request_data, *args, **kwargs):
for param in (
"business_connection_id",
"from_chat_id",
"from_story_id",
"active_period",
"post_to_chat_page",
"protect_content",
):
assert request_data.parameters.get(param) == param
return Story(chat=Chat(id=1, type=Chat.PRIVATE), id=42).to_dict()
monkeypatch.setattr(offline_bot.request, "post", make_assertion)
story = await offline_bot.repost_story(
business_connection_id="business_connection_id",
from_chat_id="from_chat_id",
from_story_id="from_story_id",
active_period="active_period",
post_to_chat_page="post_to_chat_page",
protect_content="protect_content",
)
assert story.chat.id == 1
assert story.id == 42
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
@pytest.mark.parametrize(
("passed_value", "expected_value"),
[(DEFAULT_NONE, True), (False, False), (None, None)],
)
async def test_repost_story_default_protect_content(
self, default_bot, monkeypatch, passed_value, expected_value
):
async def make_assertion(url, request_data, *args, **kwargs):
assert request_data.parameters.get("protect_content") == expected_value
return Story(chat=Chat(123, "private"), id=123).to_dict()
monkeypatch.setattr(default_bot.request, "post", make_assertion)
kwargs = {
"business_connection_id": self.bci,
"from_chat_id": 123,
"from_story_id": 456,
"active_period": dtm.timedelta(seconds=20),
}
if passed_value is not DEFAULT_NONE:
kwargs["protect_content"] = passed_value
await default_bot.repost_story(**kwargs)
+53
View File
@@ -524,6 +524,21 @@ class TestChatWithoutRequest(ChatTestBase):
monkeypatch.setattr(chat.get_bot(), "send_message", make_assertion)
assert await chat.send_message(text="test")
async def test_instance_method_send_message_draft(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id and kwargs["text"] == "test"
assert check_shortcut_signature(
Chat.send_message_draft, Bot.send_message_draft, ["chat_id"], []
)
assert await check_shortcut_call(
chat.send_message_draft, chat.get_bot(), "send_message_draft"
)
assert await check_defaults_handling(chat.send_message_draft, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "send_message_draft", make_assertion)
assert await chat.send_message_draft(draft_id=1, text="test")
async def test_instance_method_send_media_group(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id and kwargs["media"] == "test_media_group"
@@ -1495,6 +1510,44 @@ class TestChatWithoutRequest(ChatTestBase):
monkeypatch.setattr(chat.get_bot(), "decline_suggested_post", make_assertion)
assert await chat.decline_suggested_post(message_id="message_id", comment="comment")
async def test_instance_method_repost_story(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["from_chat_id"] == chat.id
assert check_shortcut_signature(
Chat.repost_story,
Bot.repost_story,
[
"from_chat_id",
],
additional_kwargs=[],
)
assert await check_shortcut_call(
chat.repost_story,
chat.get_bot(),
"repost_story",
shortcut_kwargs=["from_chat_id"],
)
assert await check_defaults_handling(chat.repost_story, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "repost_story", make_assertion)
assert await chat.repost_story(
business_connection_id="bcid",
from_story_id=123,
active_period=3600,
)
async def test_instance_method_get_gifts(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id
assert check_shortcut_signature(Chat.get_gifts, Bot.get_chat_gifts, ["chat_id"], [])
assert await check_shortcut_call(chat.get_gifts, chat.get_bot(), "get_chat_gifts")
assert await check_defaults_handling(chat.get_gifts, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "get_chat_gifts", make_assertion)
assert await chat.get_gifts()
def test_mention_html(self):
chat = Chat(id=1, type="foo")
with pytest.raises(TypeError, match="Can not create a mention to a private group chat"):
+25 -1
View File
@@ -33,6 +33,8 @@ from telegram import (
Location,
ReactionTypeCustomEmoji,
ReactionTypeEmoji,
UniqueGiftColors,
UserRating,
)
from telegram._gifts import AcceptedGiftTypes
from telegram._utils.datetime import UTC, to_timestamp
@@ -89,6 +91,9 @@ def chat_full_info(bot):
can_send_paid_media=ChatFullInfoTestBase.can_send_paid_media,
is_direct_messages=ChatFullInfoTestBase.is_direct_messages,
parent_chat=ChatFullInfoTestBase.parent_chat,
rating=ChatFullInfoTestBase.rating,
unique_gift_colors=ChatFullInfoTestBase.unique_gift_colors,
paid_message_star_count=ChatFullInfoTestBase.paid_message_star_count,
)
chat.set_bot(bot)
chat._unfreeze()
@@ -147,9 +152,19 @@ class ChatFullInfoTestBase:
first_name = "first_name"
last_name = "last_name"
can_send_paid_media = True
accepted_gift_types = AcceptedGiftTypes(True, True, True, True)
accepted_gift_types = AcceptedGiftTypes(True, True, True, True, True)
is_direct_messages = True
parent_chat = Chat(4, "channel", "channel")
rating = UserRating(level=1, rating=2, current_level_rating=3, next_level_rating=4)
unique_gift_colors = UniqueGiftColors(
model_custom_emoji_id="model_custom_emoji_id",
symbol_custom_emoji_id="symbol_custom_emoji_id",
light_theme_main_color=0xFF5733,
light_theme_other_colors=[0x33FF57, 0x3357FF],
dark_theme_main_color=0xC70039,
dark_theme_other_colors=[0x900C3F, 0x581845],
)
paid_message_star_count = 1234
class TestChatFullInfoWithoutRequest(ChatFullInfoTestBase):
@@ -207,6 +222,9 @@ class TestChatFullInfoWithoutRequest(ChatFullInfoTestBase):
"can_send_paid_media": self.can_send_paid_media,
"is_direct_messages": self.is_direct_messages,
"parent_chat": self.parent_chat.to_dict(),
"rating": self.rating.to_dict(),
"unique_gift_colors": self.unique_gift_colors.to_dict(),
"paid_message_star_count": self.paid_message_star_count,
}
cfi = ChatFullInfo.de_json(json_dict, offline_bot)
@@ -258,6 +276,9 @@ class TestChatFullInfoWithoutRequest(ChatFullInfoTestBase):
assert cfi.can_send_paid_media == self.can_send_paid_media
assert cfi.is_direct_messages == self.is_direct_messages
assert cfi.parent_chat == self.parent_chat
assert cfi.rating == self.rating
assert cfi.unique_gift_colors == self.unique_gift_colors
assert cfi.paid_message_star_count == self.paid_message_star_count
def test_de_json_localization(self, offline_bot, raw_bot, tz_bot):
json_dict = {
@@ -341,6 +362,9 @@ class TestChatFullInfoWithoutRequest(ChatFullInfoTestBase):
assert cfi_dict["max_reaction_count"] == cfi.max_reaction_count
assert cfi_dict["is_direct_messages"] == cfi.is_direct_messages
assert cfi_dict["parent_chat"] == cfi.parent_chat.to_dict()
assert cfi_dict["rating"] == cfi.rating.to_dict()
assert cfi_dict["unique_gift_colors"] == cfi.unique_gift_colors.to_dict()
assert cfi_dict["paid_message_star_count"] == cfi.paid_message_star_count
def test_time_period_properties(self, PTB_TIMEDELTA, chat_full_info):
cfi = chat_full_info
+6
View File
@@ -21,6 +21,7 @@ import datetime as dtm
import pytest
from telegram import (
Chat,
Checklist,
ChecklistTask,
ChecklistTasksAdded,
@@ -43,6 +44,7 @@ class ChecklistTaskTestBase:
MessageEntity(type="italic", offset=5, length=2),
]
completed_by_user = User(id=1, first_name="Test", last_name="User", is_bot=False)
completed_by_chat = Chat(id=-100, type=Chat.SUPERGROUP, title="Test Chat")
completion_date = dtm.datetime.now(tz=UTC).replace(microsecond=0)
@@ -53,6 +55,7 @@ def checklist_task():
text=ChecklistTaskTestBase.text,
text_entities=ChecklistTaskTestBase.text_entities,
completed_by_user=ChecklistTaskTestBase.completed_by_user,
completed_by_chat=ChecklistTaskTestBase.completed_by_chat,
completion_date=ChecklistTaskTestBase.completion_date,
)
@@ -72,6 +75,7 @@ class TestChecklistTaskWithoutRequest(ChecklistTaskTestBase):
assert clt_dict["text"] == self.text
assert clt_dict["text_entities"] == [entity.to_dict() for entity in self.text_entities]
assert clt_dict["completed_by_user"] == self.completed_by_user.to_dict()
assert clt_dict["completed_by_chat"] == self.completed_by_chat.to_dict()
assert clt_dict["completion_date"] == to_timestamp(self.completion_date)
def test_de_json(self, offline_bot):
@@ -80,6 +84,7 @@ class TestChecklistTaskWithoutRequest(ChecklistTaskTestBase):
"text": self.text,
"text_entities": [entity.to_dict() for entity in self.text_entities],
"completed_by_user": self.completed_by_user.to_dict(),
"completed_by_chat": self.completed_by_chat.to_dict(),
"completion_date": to_timestamp(self.completion_date),
}
clt = ChecklistTask.de_json(json_dict, offline_bot)
@@ -88,6 +93,7 @@ class TestChecklistTaskWithoutRequest(ChecklistTaskTestBase):
assert clt.text == self.text
assert clt.text_entities == tuple(self.text_entities)
assert clt.completed_by_user == self.completed_by_user
assert clt.completed_by_chat == self.completed_by_chat
assert clt.completion_date == self.completion_date
assert clt.api_kwargs == {}
+48 -20
View File
@@ -40,13 +40,20 @@ from tests.auxil.slots import mro_slots
async def forum_topic_object(forum_group_id, emoji_id):
return ForumTopic(
message_thread_id=forum_group_id,
name=TEST_TOPIC_NAME,
icon_color=TEST_TOPIC_ICON_COLOR,
name=ForumTopicTestBase.TEST_TOPIC_NAME,
icon_color=ForumTopicTestBase.TEST_TOPIC_ICON_COLOR,
icon_custom_emoji_id=emoji_id,
is_name_implicit=ForumTopicTestBase.is_name_implicit,
)
class TestForumTopicWithoutRequest:
class ForumTopicTestBase:
TEST_TOPIC_NAME = TEST_TOPIC_NAME
TEST_TOPIC_ICON_COLOR = TEST_TOPIC_ICON_COLOR
is_name_implicit = False
class TestForumTopicWithoutRequest(ForumTopicTestBase):
def test_slot_behaviour(self, forum_topic_object):
inst = forum_topic_object
for attr in inst.__slots__:
@@ -55,33 +62,37 @@ class TestForumTopicWithoutRequest:
async def test_expected_values(self, emoji_id, forum_group_id, forum_topic_object):
assert forum_topic_object.message_thread_id == forum_group_id
assert forum_topic_object.icon_color == TEST_TOPIC_ICON_COLOR
assert forum_topic_object.name == TEST_TOPIC_NAME
assert forum_topic_object.icon_color == self.TEST_TOPIC_ICON_COLOR
assert forum_topic_object.name == self.TEST_TOPIC_NAME
assert forum_topic_object.icon_custom_emoji_id == emoji_id
assert forum_topic_object.is_name_implicit == self.is_name_implicit
def test_de_json(self, offline_bot, emoji_id, forum_group_id):
json_dict = {
"message_thread_id": forum_group_id,
"name": TEST_TOPIC_NAME,
"icon_color": TEST_TOPIC_ICON_COLOR,
"name": self.TEST_TOPIC_NAME,
"icon_color": self.TEST_TOPIC_ICON_COLOR,
"icon_custom_emoji_id": emoji_id,
"is_name_implicit": self.is_name_implicit,
}
topic = ForumTopic.de_json(json_dict, offline_bot)
assert topic.api_kwargs == {}
assert topic.message_thread_id == forum_group_id
assert topic.icon_color == TEST_TOPIC_ICON_COLOR
assert topic.name == TEST_TOPIC_NAME
assert topic.icon_color == self.TEST_TOPIC_ICON_COLOR
assert topic.name == self.TEST_TOPIC_NAME
assert topic.icon_custom_emoji_id == emoji_id
assert topic.is_name_implicit == self.is_name_implicit
def test_to_dict(self, emoji_id, forum_group_id, forum_topic_object):
topic_dict = forum_topic_object.to_dict()
assert isinstance(topic_dict, dict)
assert topic_dict["message_thread_id"] == forum_group_id
assert topic_dict["name"] == TEST_TOPIC_NAME
assert topic_dict["icon_color"] == TEST_TOPIC_ICON_COLOR
assert topic_dict["name"] == self.TEST_TOPIC_NAME
assert topic_dict["icon_color"] == self.TEST_TOPIC_ICON_COLOR
assert topic_dict["icon_custom_emoji_id"] == emoji_id
assert topic_dict["is_name_implicit"] == self.is_name_implicit
def test_equality(self, emoji_id, forum_group_id):
a = ForumTopic(
@@ -289,10 +300,20 @@ class TestForumMethodsWithRequest:
@pytest.fixture(scope="module")
def topic_created():
return ForumTopicCreated(name=TEST_TOPIC_NAME, icon_color=TEST_TOPIC_ICON_COLOR)
return ForumTopicCreated(
name=ForumTopicCreatedTestBase.TEST_TOPIC_NAME,
icon_color=ForumTopicCreatedTestBase.TEST_TOPIC_ICON_COLOR,
is_name_implicit=ForumTopicCreatedTestBase.is_name_implicit,
)
class TestForumTopicCreatedWithoutRequest:
class ForumTopicCreatedTestBase:
TEST_TOPIC_NAME = TEST_TOPIC_NAME
TEST_TOPIC_ICON_COLOR = TEST_TOPIC_ICON_COLOR
is_name_implicit = False
class TestForumTopicCreatedWithoutRequest(ForumTopicCreatedTestBase):
def test_slot_behaviour(self, topic_created):
for attr in topic_created.__slots__:
assert getattr(topic_created, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -301,23 +322,30 @@ class TestForumTopicCreatedWithoutRequest:
)
def test_expected_values(self, topic_created):
assert topic_created.icon_color == TEST_TOPIC_ICON_COLOR
assert topic_created.name == TEST_TOPIC_NAME
assert topic_created.icon_color == self.TEST_TOPIC_ICON_COLOR
assert topic_created.name == self.TEST_TOPIC_NAME
assert topic_created.is_name_implicit == self.is_name_implicit
def test_de_json(self, offline_bot):
json_dict = {"icon_color": TEST_TOPIC_ICON_COLOR, "name": TEST_TOPIC_NAME}
json_dict = {
"icon_color": self.TEST_TOPIC_ICON_COLOR,
"name": self.TEST_TOPIC_NAME,
"is_name_implicit": self.is_name_implicit,
}
action = ForumTopicCreated.de_json(json_dict, offline_bot)
assert action.api_kwargs == {}
assert action.icon_color == TEST_TOPIC_ICON_COLOR
assert action.name == TEST_TOPIC_NAME
assert action.icon_color == self.TEST_TOPIC_ICON_COLOR
assert action.name == self.TEST_TOPIC_NAME
assert action.is_name_implicit == self.is_name_implicit
def test_to_dict(self, topic_created):
action_dict = topic_created.to_dict()
assert isinstance(action_dict, dict)
assert action_dict["name"] == TEST_TOPIC_NAME
assert action_dict["icon_color"] == TEST_TOPIC_ICON_COLOR
assert action_dict["name"] == self.TEST_TOPIC_NAME
assert action_dict["icon_color"] == self.TEST_TOPIC_ICON_COLOR
assert action_dict["is_name_implicit"] == self.is_name_implicit
def test_equality(self, emoji_id):
a = ForumTopicCreated(name=TEST_TOPIC_NAME, icon_color=TEST_TOPIC_ICON_COLOR)
+117 -2
View File
@@ -21,12 +21,77 @@ from collections.abc import Sequence
import pytest
from telegram import BotCommand, Chat, Gift, GiftInfo, Gifts, MessageEntity, Sticker
from telegram._gifts import AcceptedGiftTypes
from telegram._gifts import AcceptedGiftTypes, GiftBackground
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram.request import RequestData
from tests.auxil.slots import mro_slots
@pytest.fixture
def gift_background():
return GiftBackground(
center_color=GiftBackgroundTestBase.center_color,
edge_color=GiftBackgroundTestBase.edge_color,
text_color=GiftBackgroundTestBase.text_color,
)
class GiftBackgroundTestBase:
center_color = 0xFFFFFF
edge_color = 0x000000
text_color = 0xFF0000
class TestGiftBackgroundWithoutRequest(GiftBackgroundTestBase):
def test_slot_behaviour(self, gift_background):
for attr in gift_background.__slots__:
assert getattr(gift_background, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(gift_background)) == len(set(mro_slots(gift_background))), (
"duplicate slot"
)
def test_de_json(self, offline_bot):
json_dict = {
"center_color": self.center_color,
"edge_color": self.edge_color,
"text_color": self.text_color,
}
gift_background = GiftBackground.de_json(json_dict, offline_bot)
assert gift_background.api_kwargs == {}
assert gift_background.center_color == self.center_color
assert gift_background.edge_color == self.edge_color
assert gift_background.text_color == self.text_color
def test_to_dict(self, gift_background):
json_dict = gift_background.to_dict()
assert json_dict["center_color"] == self.center_color
assert json_dict["edge_color"] == self.edge_color
assert json_dict["text_color"] == self.text_color
def test_equality(self, gift_background):
a = gift_background
b = GiftBackground(
self.center_color,
self.edge_color,
self.text_color,
)
c = GiftBackground(
0x000000,
self.edge_color,
self.text_color,
)
d = BotCommand("start", "description")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
@pytest.fixture
def gift(request):
return Gift(
@@ -37,6 +102,12 @@ def gift(request):
remaining_count=GiftTestBase.remaining_count,
upgrade_star_count=GiftTestBase.upgrade_star_count,
publisher_chat=GiftTestBase.publisher_chat,
personal_total_count=GiftTestBase.personal_total_count,
personal_remaining_count=GiftTestBase.personal_remaining_count,
background=GiftTestBase.background,
is_premium=GiftTestBase.is_premium,
has_colors=GiftTestBase.has_colors,
unique_gift_variant_count=GiftTestBase.unique_gift_variant_count,
)
@@ -56,6 +127,12 @@ class GiftTestBase:
remaining_count = 5
upgrade_star_count = 10
publisher_chat = Chat(1, Chat.PRIVATE)
personal_total_count = 37
personal_remaining_count = 23
background = GiftBackground(0xFFFFFF, 0x000000, 0xFF0000)
is_premium = True
has_colors = True
unique_gift_variant_count = 42
class TestGiftWithoutRequest(GiftTestBase):
@@ -73,6 +150,12 @@ class TestGiftWithoutRequest(GiftTestBase):
"remaining_count": self.remaining_count,
"upgrade_star_count": self.upgrade_star_count,
"publisher_chat": self.publisher_chat.to_dict(),
"personal_total_count": self.personal_total_count,
"personal_remaining_count": self.personal_remaining_count,
"background": self.background.to_dict(),
"is_premium": self.is_premium,
"has_colors": self.has_colors,
"unique_gift_variant_count": self.unique_gift_variant_count,
}
gift = Gift.de_json(json_dict, offline_bot)
assert gift.api_kwargs == {}
@@ -84,6 +167,12 @@ class TestGiftWithoutRequest(GiftTestBase):
assert gift.remaining_count == self.remaining_count
assert gift.upgrade_star_count == self.upgrade_star_count
assert gift.publisher_chat == self.publisher_chat
assert gift.personal_total_count == self.personal_total_count
assert gift.personal_remaining_count == self.personal_remaining_count
assert gift.background == self.background
assert gift.is_premium == self.is_premium
assert gift.has_colors == self.has_colors
assert gift.unique_gift_variant_count == self.unique_gift_variant_count
def test_to_dict(self, gift):
gift_dict = gift.to_dict()
@@ -96,6 +185,12 @@ class TestGiftWithoutRequest(GiftTestBase):
assert gift_dict["remaining_count"] == self.remaining_count
assert gift_dict["upgrade_star_count"] == self.upgrade_star_count
assert gift_dict["publisher_chat"] == self.publisher_chat.to_dict()
assert gift_dict["personal_total_count"] == self.personal_total_count
assert gift_dict["personal_remaining_count"] == self.personal_remaining_count
assert gift_dict["background"] == self.background.to_dict()
assert gift_dict["is_premium"] == self.is_premium
assert gift_dict["has_colors"] == self.has_colors
assert gift_dict["unique_gift_variant_count"] == self.unique_gift_variant_count
def test_equality(self, gift):
a = gift
@@ -316,6 +411,8 @@ def gift_info():
text=GiftInfoTestBase.text,
entities=GiftInfoTestBase.entities,
is_private=GiftInfoTestBase.is_private,
is_upgrade_separate=GiftInfoTestBase.is_upgrade_separate,
unique_gift_number=GiftInfoTestBase.unique_gift_number,
)
@@ -338,6 +435,8 @@ class GiftInfoTestBase:
MessageEntity(MessageEntity.ITALIC, 5, 8),
)
is_private = True
is_upgrade_separate = False
unique_gift_number = 42
class TestGiftInfoWithoutRequest(GiftInfoTestBase):
@@ -356,6 +455,8 @@ class TestGiftInfoWithoutRequest(GiftInfoTestBase):
"text": self.text,
"entities": [e.to_dict() for e in self.entities],
"is_private": self.is_private,
"is_upgrade_separate": self.is_upgrade_separate,
"unique_gift_number": self.unique_gift_number,
}
gift_info = GiftInfo.de_json(json_dict, offline_bot)
assert gift_info.api_kwargs == {}
@@ -367,6 +468,8 @@ class TestGiftInfoWithoutRequest(GiftInfoTestBase):
assert gift_info.text == self.text
assert gift_info.entities == self.entities
assert gift_info.is_private == self.is_private
assert gift_info.is_upgrade_separate == self.is_upgrade_separate
assert gift_info.unique_gift_number == self.unique_gift_number
def test_to_dict(self, gift_info):
json_dict = gift_info.to_dict()
@@ -378,6 +481,8 @@ class TestGiftInfoWithoutRequest(GiftInfoTestBase):
assert json_dict["text"] == self.text
assert json_dict["entities"] == [e.to_dict() for e in self.entities]
assert json_dict["is_private"] == self.is_private
assert json_dict["is_upgrade_separate"] == self.is_upgrade_separate
assert json_dict["unique_gift_number"] == self.unique_gift_number
def test_parse_entity(self, gift_info):
entity = MessageEntity(MessageEntity.BOLD, 0, 4)
@@ -430,6 +535,7 @@ def accepted_gift_types():
limited_gifts=AcceptedGiftTypesTestBase.limited_gifts,
unique_gifts=AcceptedGiftTypesTestBase.unique_gifts,
premium_subscription=AcceptedGiftTypesTestBase.premium_subscription,
gifts_from_channels=AcceptedGiftTypesTestBase.gifts_from_channels,
)
@@ -438,6 +544,7 @@ class AcceptedGiftTypesTestBase:
limited_gifts = True
unique_gifts = True
premium_subscription = True
gifts_from_channels = False
class TestAcceptedGiftTypesWithoutRequest(AcceptedGiftTypesTestBase):
@@ -454,6 +561,7 @@ class TestAcceptedGiftTypesWithoutRequest(AcceptedGiftTypesTestBase):
"limited_gifts": self.limited_gifts,
"unique_gifts": self.unique_gifts,
"premium_subscription": self.premium_subscription,
"gifts_from_channels": self.gifts_from_channels,
}
accepted_gift_types = AcceptedGiftTypes.de_json(json_dict, offline_bot)
assert accepted_gift_types.api_kwargs == {}
@@ -461,6 +569,7 @@ class TestAcceptedGiftTypesWithoutRequest(AcceptedGiftTypesTestBase):
assert accepted_gift_types.limited_gifts == self.limited_gifts
assert accepted_gift_types.unique_gifts == self.unique_gifts
assert accepted_gift_types.premium_subscription == self.premium_subscription
assert accepted_gift_types.gifts_from_channels == self.gifts_from_channels
def test_to_dict(self, accepted_gift_types):
json_dict = accepted_gift_types.to_dict()
@@ -468,17 +577,23 @@ class TestAcceptedGiftTypesWithoutRequest(AcceptedGiftTypesTestBase):
assert json_dict["limited_gifts"] == self.limited_gifts
assert json_dict["unique_gifts"] == self.unique_gifts
assert json_dict["premium_subscription"] == self.premium_subscription
assert json_dict["gifts_from_channels"] == self.gifts_from_channels
def test_equality(self, accepted_gift_types):
a = accepted_gift_types
b = AcceptedGiftTypes(
self.unlimited_gifts, self.limited_gifts, self.unique_gifts, self.premium_subscription
self.unlimited_gifts,
self.limited_gifts,
self.unique_gifts,
self.premium_subscription,
self.gifts_from_channels,
)
c = AcceptedGiftTypes(
not self.unlimited_gifts,
self.limited_gifts,
self.unique_gifts,
self.premium_subscription,
self.gifts_from_channels,
)
d = BotCommand("start", "description")
+50 -7
View File
@@ -268,20 +268,21 @@ def message(bot):
{
"unique_gift": UniqueGiftInfo(
gift=UniqueGift(
"human_readable_name",
"unique_name",
2,
UniqueGiftModel(
gift_id="gift_id",
base_name="human_readable_name",
name="unique_name",
number=2,
model=UniqueGiftModel(
"model_name",
Sticker("file_id1", "file_unique_id1", 512, 512, False, False, "regular"),
10,
),
UniqueGiftSymbol(
symbol=UniqueGiftSymbol(
"symbol_name",
Sticker("file_id2", "file_unique_id2", 512, 512, True, True, "mask"),
20,
),
UniqueGiftBackdrop(
backdrop=UniqueGiftBackdrop(
"backdrop_name",
UniqueGiftBackdropColors(0x00FF00, 0xEE00FF, 0xAA22BB, 0x20FE8F),
30,
@@ -420,6 +421,15 @@ def message(bot):
send_date=dtm.datetime.utcnow(),
)
},
{
"gift_upgrade_sent": GiftInfo(
gift=Gift(
"gift_id",
Sticker("file_id", "file_unique_id", 512, 512, False, False, "regular"),
5,
)
)
},
],
ids=[
"reply",
@@ -510,6 +520,7 @@ def message(bot):
"suggested_post_approved",
"suggested_post_approval_failed",
"suggested_post_info",
"gift_upgrade_sent",
],
)
def message_params(bot, request):
@@ -735,7 +746,8 @@ class TestMessageWithoutRequest(MessageTestBase):
message_thread_id = await method(*args, message_thread_id=None)
assert message_thread_id is None
if bot_method_name == "send_chat_action":
# These methods do not accept `do_quote` as passed below
if bot_method_name in ["send_chat_action", "send_message_draft"]:
return
message_thread_id = await method(
@@ -1831,6 +1843,37 @@ class TestMessageWithoutRequest(MessageTestBase):
message, message.reply_html, "send_message", ["test"], monkeypatch
)
async def test_reply_text_draft(self, monkeypatch, message):
async def make_assertion(*_, **kwargs):
id_ = kwargs["chat_id"] == message.chat_id
text = kwargs["text"] == "test"
return id_ and text
assert check_shortcut_signature(
Message.reply_text_draft,
Bot.send_message_draft,
["chat_id"],
[],
annotation_overrides={"message_thread_id": (ODVInput[int], DEFAULT_NONE)},
)
assert await check_shortcut_call(
message.reply_text_draft,
message.get_bot(),
"send_message_draft",
skip_params=[""],
shortcut_kwargs=["chat_id"],
)
assert await check_defaults_handling(
message.reply_text_draft, message.get_bot(), no_default_kwargs={"message_thread_id"}
)
monkeypatch.setattr(message.get_bot(), "send_message_draft", make_assertion)
assert await message.reply_text_draft(draft_id=1, text="test")
await self.check_thread_id_parsing(
message, message.reply_text_draft, "send_message_draft", [1, "test"], monkeypatch
)
async def test_reply_media_group(self, monkeypatch, message):
async def make_assertion(*_, **kwargs):
id_ = kwargs["chat_id"] == message.chat_id
+10 -3
View File
@@ -159,6 +159,9 @@ PTB_EXTRA_PARAMS = {
"InputStoryContent": {"type"}, # attributes common to all subclasses
"StoryAreaType": {"type"}, # attributes common to all subclasses
"InputProfilePhoto": {"type"}, # attributes common to all subclasses
# backwards compatibility for api 9.3 changes
# tags: deprecated NEXT.VERSION, bot api 9.3
"UniqueGiftInfo": {"last_resale_star_count"},
}
@@ -191,8 +194,6 @@ PTB_IGNORED_PARAMS = {
r"OwnedGift\w+": {"type"},
r"InputStoryContent\w+": {"type"},
r"StoryAreaType\w+": {"type"},
# Official API field not yet implemented in PTB
"User": {"has_topics_enabled"},
}
@@ -208,6 +209,9 @@ IGNORED_PARAM_REQUIREMENTS = {
"send_venue": {"latitude", "longitude", "title", "address"},
"send_contact": {"phone_number", "first_name"},
# ---->
# backwards compatibility for api 9.3 changes
# tags: deprecated NEXT.VERSION, bot api 9.3
"UniqueGift": {"gift_id"},
}
@@ -216,7 +220,10 @@ def ignored_param_requirements(object_name: str) -> set[str]:
# Arguments that are optional arguments for now for backwards compatibility
BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = {}
BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = {
# tags: deprecated NEXT.VERSION, bot api 9.3
"get_business_account_gifts": {"exclude_limited"},
}
def backwards_compat_kwargs(object_name: str) -> set[str]:
+12
View File
@@ -60,6 +60,7 @@ class OwnedGiftTestBase:
star_count=5,
)
unique_gift = UniqueGift(
gift_id="gift_id",
base_name="human_readable",
name="unique_name",
number=10,
@@ -96,6 +97,8 @@ class OwnedGiftTestBase:
can_be_transferred = True
transfer_star_count = 300
next_transfer_date = dtm.datetime.now(tz=UTC).replace(microsecond=0)
is_upgrade_separate = False
unique_gift_number = 37
class TestOwnedGiftWithoutRequest(OwnedGiftTestBase):
@@ -183,6 +186,8 @@ def owned_gift_regular():
was_refunded=TestOwnedGiftRegularWithoutRequest.was_refunded,
convert_star_count=TestOwnedGiftRegularWithoutRequest.convert_star_count,
prepaid_upgrade_star_count=TestOwnedGiftRegularWithoutRequest.prepaid_upgrade_star_count,
is_upgrade_separate=TestOwnedGiftRegularWithoutRequest.is_upgrade_separate,
unique_gift_number=TestOwnedGiftRegularWithoutRequest.unique_gift_number,
)
@@ -209,6 +214,8 @@ class TestOwnedGiftRegularWithoutRequest(OwnedGiftTestBase):
"was_refunded": self.was_refunded,
"convert_star_count": self.convert_star_count,
"prepaid_upgrade_star_count": self.prepaid_upgrade_star_count,
"is_upgrade_separate": self.is_upgrade_separate,
"unique_gift_number": self.unique_gift_number,
}
ogr = OwnedGiftRegular.de_json(json_dict, offline_bot)
assert ogr.gift == self.gift
@@ -223,6 +230,8 @@ class TestOwnedGiftRegularWithoutRequest(OwnedGiftTestBase):
assert ogr.was_refunded == self.was_refunded
assert ogr.convert_star_count == self.convert_star_count
assert ogr.prepaid_upgrade_star_count == self.prepaid_upgrade_star_count
assert ogr.is_upgrade_separate == self.is_upgrade_separate
assert ogr.unique_gift_number == self.unique_gift_number
assert ogr.api_kwargs == {}
def test_to_dict(self, owned_gift_regular):
@@ -241,6 +250,8 @@ class TestOwnedGiftRegularWithoutRequest(OwnedGiftTestBase):
assert json_dict["was_refunded"] == self.was_refunded
assert json_dict["convert_star_count"] == self.convert_star_count
assert json_dict["prepaid_upgrade_star_count"] == self.prepaid_upgrade_star_count
assert json_dict["is_upgrade_separate"] == self.is_upgrade_separate
assert json_dict["unique_gift_number"] == self.unique_gift_number
def test_parse_entity(self, owned_gift_regular):
entity = MessageEntity(MessageEntity.BOLD, 0, 4)
@@ -393,6 +404,7 @@ class OwnedGiftsTestBase:
),
OwnedGiftUnique(
gift=UniqueGift(
gift_id="gift_id",
base_name="human_readable",
name="unique_name",
number=10,
+39 -3
View File
@@ -18,13 +18,20 @@
import pytest
from telegram import Chat, Story
from telegram import Bot, Chat, Story
from tests.auxil.bot_method_checks import (
check_defaults_handling,
check_shortcut_call,
check_shortcut_signature,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def story():
return Story(StoryTestBase.chat, StoryTestBase.id)
def story(bot):
story = Story(StoryTestBase.chat, StoryTestBase.id)
story.set_bot(bot)
return story
class StoryTestBase:
@@ -69,3 +76,32 @@ class TestStoryWithoutRequest(StoryTestBase):
assert a != e
assert hash(a) != hash(e)
async def test_instance_method_repost(self, monkeypatch, story):
async def make_assertion(*_, **kwargs):
chat_id = kwargs["from_chat_id"] == story.chat.id
story_id = kwargs["from_story_id"] == story.id
return chat_id and story_id
assert check_shortcut_signature(
Story.repost,
Bot.repost_story,
[
"from_chat_id",
"from_story_id",
],
additional_kwargs=[],
)
assert await check_shortcut_call(
story.repost,
story.get_bot(),
"repost_story",
shortcut_kwargs=["from_chat_id", "from_story_id"],
)
assert await check_defaults_handling(story.repost, story.get_bot())
monkeypatch.setattr(story.get_bot(), "repost_story", make_assertion)
assert await story.repost(
business_connection_id="bcid",
active_period=3600,
)
+180 -21
View File
@@ -28,18 +28,107 @@ from telegram import (
UniqueGift,
UniqueGiftBackdrop,
UniqueGiftBackdropColors,
UniqueGiftColors,
UniqueGiftInfo,
UniqueGiftModel,
UniqueGiftSymbol,
)
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import UniqueGiftInfoOrigin
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.slots import mro_slots
@pytest.fixture
def unique_gift_colors():
return UniqueGiftColors(
model_custom_emoji_id=UniqueGiftColorsTestBase.model_custom_emoji_id,
symbol_custom_emoji_id=UniqueGiftColorsTestBase.symbol_custom_emoji_id,
light_theme_main_color=UniqueGiftColorsTestBase.light_theme_main_color,
light_theme_other_colors=UniqueGiftColorsTestBase.light_theme_other_colors,
dark_theme_main_color=UniqueGiftColorsTestBase.dark_theme_main_color,
dark_theme_other_colors=UniqueGiftColorsTestBase.dark_theme_other_colors,
)
class UniqueGiftColorsTestBase:
model_custom_emoji_id = "model_emoji_id"
symbol_custom_emoji_id = "symbol_emoji_id"
light_theme_main_color = 0xFFFFFF
light_theme_other_colors = [0xAAAAAA, 0xBBBBBB]
dark_theme_main_color = 0x000000
dark_theme_other_colors = [0x111111, 0x222222]
class TestUniqueGiftColorsWithoutRequest(UniqueGiftColorsTestBase):
def test_slot_behaviour(self, unique_gift_colors):
for attr in unique_gift_colors.__slots__:
assert getattr(unique_gift_colors, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(unique_gift_colors)) == len(set(mro_slots(unique_gift_colors))), (
"duplicate slot"
)
def test_de_json(self, offline_bot):
json_dict = {
"model_custom_emoji_id": self.model_custom_emoji_id,
"symbol_custom_emoji_id": self.symbol_custom_emoji_id,
"light_theme_main_color": self.light_theme_main_color,
"light_theme_other_colors": self.light_theme_other_colors,
"dark_theme_main_color": self.dark_theme_main_color,
"dark_theme_other_colors": self.dark_theme_other_colors,
}
unique_gift_colors = UniqueGiftColors.de_json(json_dict, offline_bot)
assert unique_gift_colors.api_kwargs == {}
assert unique_gift_colors.model_custom_emoji_id == self.model_custom_emoji_id
assert unique_gift_colors.symbol_custom_emoji_id == self.symbol_custom_emoji_id
assert unique_gift_colors.light_theme_main_color == self.light_theme_main_color
assert unique_gift_colors.light_theme_other_colors == tuple(self.light_theme_other_colors)
assert unique_gift_colors.dark_theme_main_color == self.dark_theme_main_color
assert unique_gift_colors.dark_theme_other_colors == tuple(self.dark_theme_other_colors)
def test_to_dict(self, unique_gift_colors):
json_dict = unique_gift_colors.to_dict()
assert json_dict["model_custom_emoji_id"] == self.model_custom_emoji_id
assert json_dict["symbol_custom_emoji_id"] == self.symbol_custom_emoji_id
assert json_dict["light_theme_main_color"] == self.light_theme_main_color
assert json_dict["light_theme_other_colors"] == self.light_theme_other_colors
assert json_dict["dark_theme_main_color"] == self.dark_theme_main_color
assert json_dict["dark_theme_other_colors"] == self.dark_theme_other_colors
def test_equality(self, unique_gift_colors):
a = unique_gift_colors
b = UniqueGiftColors(
self.model_custom_emoji_id,
self.symbol_custom_emoji_id,
self.light_theme_main_color,
self.light_theme_other_colors,
self.dark_theme_main_color,
self.dark_theme_other_colors,
)
c = UniqueGiftColors(
"other_model_emoji_id",
self.symbol_custom_emoji_id,
self.light_theme_main_color,
self.light_theme_other_colors,
self.dark_theme_main_color,
self.dark_theme_other_colors,
)
d = BotCommand("start", "description")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
@pytest.fixture
def unique_gift():
return UniqueGift(
gift_id=UniqueGiftTestBase.gift_id,
base_name=UniqueGiftTestBase.base_name,
name=UniqueGiftTestBase.name,
number=UniqueGiftTestBase.number,
@@ -47,10 +136,14 @@ def unique_gift():
symbol=UniqueGiftTestBase.symbol,
backdrop=UniqueGiftTestBase.backdrop,
publisher_chat=UniqueGiftTestBase.publisher_chat,
is_premium=UniqueGiftTestBase.is_premium,
is_from_blockchain=UniqueGiftTestBase.is_from_blockchain,
colors=UniqueGiftTestBase.colors,
)
class UniqueGiftTestBase:
gift_id = "gift_id"
base_name = "human_readable"
name = "unique_name"
number = 10
@@ -70,6 +163,16 @@ class UniqueGiftTestBase:
rarity_per_mille=30,
)
publisher_chat = Chat(1, Chat.PRIVATE)
is_premium = False
is_from_blockchain = True
colors = UniqueGiftColors(
model_custom_emoji_id="M",
symbol_custom_emoji_id="S",
light_theme_main_color=0xFFFFFF,
light_theme_other_colors=[0xAAAAAA],
dark_theme_main_color=0x000000,
dark_theme_other_colors=[0x111111],
)
class TestUniqueGiftWithoutRequest(UniqueGiftTestBase):
@@ -80,6 +183,7 @@ class TestUniqueGiftWithoutRequest(UniqueGiftTestBase):
def test_de_json(self, offline_bot):
json_dict = {
"gift_id": self.gift_id,
"base_name": self.base_name,
"name": self.name,
"number": self.number,
@@ -87,6 +191,9 @@ class TestUniqueGiftWithoutRequest(UniqueGiftTestBase):
"symbol": self.symbol.to_dict(),
"backdrop": self.backdrop.to_dict(),
"publisher_chat": self.publisher_chat.to_dict(),
"is_premium": self.is_premium,
"is_from_blockchain": self.is_from_blockchain,
"colors": self.colors.to_dict(),
}
unique_gift = UniqueGift.de_json(json_dict, offline_bot)
assert unique_gift.api_kwargs == {}
@@ -98,11 +205,15 @@ class TestUniqueGiftWithoutRequest(UniqueGiftTestBase):
assert unique_gift.symbol == self.symbol
assert unique_gift.backdrop == self.backdrop
assert unique_gift.publisher_chat == self.publisher_chat
assert unique_gift.is_premium == self.is_premium
assert unique_gift.is_from_blockchain == self.is_from_blockchain
assert unique_gift.colors == self.colors
def test_to_dict(self, unique_gift):
gift_dict = unique_gift.to_dict()
assert isinstance(gift_dict, dict)
assert gift_dict["gift_id"] == self.gift_id
assert gift_dict["base_name"] == self.base_name
assert gift_dict["name"] == self.name
assert gift_dict["number"] == self.number
@@ -110,26 +221,31 @@ class TestUniqueGiftWithoutRequest(UniqueGiftTestBase):
assert gift_dict["symbol"] == self.symbol.to_dict()
assert gift_dict["backdrop"] == self.backdrop.to_dict()
assert gift_dict["publisher_chat"] == self.publisher_chat.to_dict()
assert gift_dict["is_premium"] == self.is_premium
assert gift_dict["is_from_blockchain"] == self.is_from_blockchain
assert gift_dict["colors"] == self.colors.to_dict()
def test_equality(self, unique_gift):
a = unique_gift
b = UniqueGift(
self.base_name,
self.name,
self.number,
self.model,
self.symbol,
self.backdrop,
self.publisher_chat,
gift_id=self.gift_id,
base_name=self.base_name,
name=self.name,
number=self.number,
model=self.model,
symbol=self.symbol,
backdrop=self.backdrop,
publisher_chat=self.publisher_chat,
)
c = UniqueGift(
"other_base_name",
self.name,
self.number,
self.model,
self.symbol,
self.backdrop,
self.publisher_chat,
gift_id=self.gift_id,
base_name="other_base_name",
name=self.name,
number=self.number,
model=self.model,
symbol=self.symbol,
backdrop=self.backdrop,
publisher_chat=self.publisher_chat,
)
d = BotCommand("start", "description")
@@ -142,6 +258,19 @@ class TestUniqueGiftWithoutRequest(UniqueGiftTestBase):
assert a != d
assert hash(a) != hash(d)
def test_gift_id_required_workaround(self):
# tags: deprecated NEXT.VERSION, bot api 9.3
with pytest.raises(TypeError, match="`gift_id` is a required"):
UniqueGift(
base_name=self.base_name,
name=self.name,
number=self.number,
model=self.model,
symbol=self.symbol,
backdrop=self.backdrop,
publisher_chat=self.publisher_chat,
)
@pytest.fixture
def unique_gift_model():
@@ -396,26 +525,29 @@ def unique_gift_info():
owned_gift_id=UniqueGiftInfoTestBase.owned_gift_id,
transfer_star_count=UniqueGiftInfoTestBase.transfer_star_count,
last_resale_star_count=UniqueGiftInfoTestBase.last_resale_star_count,
last_resale_currency=UniqueGiftInfoTestBase.last_resale_currency,
last_resale_amount=UniqueGiftInfoTestBase.last_resale_amount,
next_transfer_date=UniqueGiftInfoTestBase.next_transfer_date,
)
class UniqueGiftInfoTestBase:
gift = UniqueGift(
"human_readable_name",
"unique_name",
10,
UniqueGiftModel(
gift_id="gift_id",
base_name="human_readable_name",
name="unique_name",
number=10,
model=UniqueGiftModel(
name="model_name",
sticker=Sticker("file_id1", "file_unique_id1", 512, 512, False, False, "regular"),
rarity_per_mille=10,
),
UniqueGiftSymbol(
symbol=UniqueGiftSymbol(
name="symbol_name",
sticker=Sticker("file_id2", "file_unique_id2", 512, 512, True, True, "mask"),
rarity_per_mille=20,
),
UniqueGiftBackdrop(
backdrop=UniqueGiftBackdrop(
name="backdrop_name",
colors=UniqueGiftBackdropColors(0x00FF00, 0xEE00FF, 0xAA22BB, 0x20FE8F),
rarity_per_mille=2,
@@ -425,6 +557,8 @@ class UniqueGiftInfoTestBase:
owned_gift_id = "some_id"
transfer_star_count = 10
last_resale_star_count = 5
last_resale_currency = "XTR"
last_resale_amount = 1234
next_transfer_date = dtm.datetime.now(tz=UTC).replace(microsecond=0)
@@ -443,6 +577,8 @@ class TestUniqueGiftInfoWithoutRequest(UniqueGiftInfoTestBase):
"owned_gift_id": self.owned_gift_id,
"transfer_star_count": self.transfer_star_count,
"last_resale_star_count": self.last_resale_star_count,
"last_resale_currency": self.last_resale_currency,
"last_resale_amount": self.last_resale_amount,
"next_transfer_date": to_timestamp(self.next_transfer_date),
}
unique_gift_info = UniqueGiftInfo.de_json(json_dict, offline_bot)
@@ -452,6 +588,8 @@ class TestUniqueGiftInfoWithoutRequest(UniqueGiftInfoTestBase):
assert unique_gift_info.owned_gift_id == self.owned_gift_id
assert unique_gift_info.transfer_star_count == self.transfer_star_count
assert unique_gift_info.last_resale_star_count == self.last_resale_star_count
assert unique_gift_info.last_resale_currency == self.last_resale_currency
assert unique_gift_info.last_resale_amount == self.last_resale_amount
assert unique_gift_info.next_transfer_date == self.next_transfer_date
def test_de_json_localization(self, tz_bot, offline_bot, raw_bot):
@@ -461,6 +599,8 @@ class TestUniqueGiftInfoWithoutRequest(UniqueGiftInfoTestBase):
"owned_gift_id": self.owned_gift_id,
"transfer_star_count": self.transfer_star_count,
"last_resale_star_count": self.last_resale_star_count,
"last_resale_currency": self.last_resale_currency,
"last_resale_amount": self.last_resale_amount,
"next_transfer_date": to_timestamp(self.next_transfer_date),
}
@@ -484,7 +624,8 @@ class TestUniqueGiftInfoWithoutRequest(UniqueGiftInfoTestBase):
assert json_dict["origin"] == self.origin
assert json_dict["owned_gift_id"] == self.owned_gift_id
assert json_dict["transfer_star_count"] == self.transfer_star_count
assert json_dict["last_resale_star_count"] == self.last_resale_star_count
assert json_dict["last_resale_currency"] == self.last_resale_currency
assert json_dict["last_resale_amount"] == self.last_resale_amount
assert json_dict["next_transfer_date"] == to_timestamp(self.next_transfer_date)
def test_enum_type_conversion(self, unique_gift_info):
@@ -507,3 +648,21 @@ class TestUniqueGiftInfoWithoutRequest(UniqueGiftInfoTestBase):
assert a != d
assert hash(a) != hash(d)
def test_last_resale_star_count_argument_deprecation(self):
with pytest.warns(PTBDeprecationWarning, match=r"9\.3.*last_resale_star_count") as record:
UniqueGiftInfo(
gift=self.gift,
origin=UniqueGiftInfo.TRANSFER,
last_resale_star_count=self.last_resale_star_count,
)
assert record[0].category == PTBDeprecationWarning
assert record[0].filename == __file__, "wrong stacklevel!"
def test_last_resale_star_count_attribute_deprecation(self, unique_gift_info):
with pytest.warns(PTBDeprecationWarning, match=r"9\.3.*last_resale_star_count") as record:
assert unique_gift_info.last_resale_star_count == self.last_resale_star_count
assert record[0].category == PTBDeprecationWarning
assert record[0].filename == __file__, "wrong stacklevel!"
+62
View File
@@ -44,6 +44,7 @@ def json_dict():
"added_to_attachment_menu": UserTestBase.added_to_attachment_menu,
"can_connect_to_business": UserTestBase.can_connect_to_business,
"has_main_web_app": UserTestBase.has_main_web_app,
"has_topics_enabled": UserTestBase.has_topics_enabled,
}
@@ -63,6 +64,7 @@ def user(bot):
added_to_attachment_menu=UserTestBase.added_to_attachment_menu,
can_connect_to_business=UserTestBase.can_connect_to_business,
has_main_web_app=UserTestBase.has_main_web_app,
has_topics_enabled=UserTestBase.has_topics_enabled,
)
user.set_bot(bot)
user._unfreeze()
@@ -83,6 +85,7 @@ class UserTestBase:
added_to_attachment_menu = False
can_connect_to_business = True
has_main_web_app = False
has_topics_enabled = False
class TestUserWithoutRequest(UserTestBase):
@@ -108,6 +111,7 @@ class TestUserWithoutRequest(UserTestBase):
assert user.added_to_attachment_menu == self.added_to_attachment_menu
assert user.can_connect_to_business == self.can_connect_to_business
assert user.has_main_web_app == self.has_main_web_app
assert user.has_topics_enabled == self.has_topics_enabled
def test_to_dict(self, user):
user_dict = user.to_dict()
@@ -126,6 +130,7 @@ class TestUserWithoutRequest(UserTestBase):
assert user_dict["added_to_attachment_menu"] == user.added_to_attachment_menu
assert user_dict["can_connect_to_business"] == user.can_connect_to_business
assert user_dict["has_main_web_app"] == user.has_main_web_app
assert user_dict["has_topics_enabled"] == user.has_topics_enabled
def test_equality(self):
a = User(self.id_, self.first_name, self.is_bot, self.last_name)
@@ -231,6 +236,25 @@ class TestUserWithoutRequest(UserTestBase):
monkeypatch.setattr(user.get_bot(), "send_message", make_assertion)
assert await user.send_message("test")
async def test_instance_method_send_message_draft(self, monkeypatch, user):
async def make_assertion(*_, **kwargs):
return (
kwargs["chat_id"] == user.id
and kwargs["draft_id"] == 123
and kwargs["text"] == "test"
)
assert check_shortcut_signature(
User.send_message_draft, Bot.send_message_draft, ["chat_id"], []
)
assert await check_shortcut_call(
user.send_message_draft, user.get_bot(), "send_message_draft"
)
assert await check_defaults_handling(user.send_message_draft, user.get_bot())
monkeypatch.setattr(user.get_bot(), "send_message_draft", make_assertion)
assert await user.send_message_draft(123, "test")
async def test_instance_method_send_photo(self, monkeypatch, user):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == user.id and kwargs["photo"] == "test_photo"
@@ -805,3 +829,41 @@ class TestUserWithoutRequest(UserTestBase):
monkeypatch.setattr(user.get_bot(), "remove_user_verification", make_assertion)
assert await user.remove_verification()
async def test_instance_method_repost_story(self, monkeypatch, user):
async def make_assertion(*_, **kwargs):
return kwargs["from_chat_id"] == user.id
assert check_shortcut_signature(
User.repost_story,
Bot.repost_story,
[
"from_chat_id",
],
additional_kwargs=[],
)
assert await check_shortcut_call(
user.repost_story,
user.get_bot(),
"repost_story",
shortcut_kwargs=["from_chat_id"],
)
assert await check_defaults_handling(user.repost_story, user.get_bot())
monkeypatch.setattr(user.get_bot(), "repost_story", make_assertion)
assert await user.repost_story(
business_connection_id="bcid",
from_story_id=123,
active_period=3600,
)
async def test_instance_method_get_gifts(self, monkeypatch, user):
async def make_assertion(*_, **kwargs):
return kwargs["user_id"] == user.id
assert check_shortcut_signature(user.get_gifts, Bot.get_user_gifts, ["user_id"], [])
assert await check_shortcut_call(user.get_gifts, user.get_bot(), "get_user_gifts")
assert await check_defaults_handling(user.get_gifts, user.get_bot())
monkeypatch.setattr(user.get_bot(), "get_user_gifts", make_assertion)
assert await user.get_gifts()
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2026
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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/].
import pytest
from telegram import BotCommand, UserRating
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def user_rating():
return UserRating(
level=UserRatingTestBase.level,
rating=UserRatingTestBase.rating,
current_level_rating=UserRatingTestBase.current_level_rating,
next_level_rating=UserRatingTestBase.next_level_rating,
)
class UserRatingTestBase:
level = 2
rating = 120
current_level_rating = 100
next_level_rating = 180
class TestUserRatingWithoutRequest(UserRatingTestBase):
def test_slot_behaviour(self, user_rating):
for attr in user_rating.__slots__:
assert getattr(user_rating, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(user_rating)) == len(set(mro_slots(user_rating))), "duplicate slot"
def test_de_json_with_next(self, offline_bot):
json_dict = {
"level": self.level,
"rating": self.rating,
"current_level_rating": self.current_level_rating,
"next_level_rating": self.next_level_rating,
}
ur = UserRating.de_json(json_dict, offline_bot)
assert ur.api_kwargs == {}
assert ur.level == self.level
assert ur.rating == self.rating
assert ur.current_level_rating == self.current_level_rating
assert ur.next_level_rating == self.next_level_rating
def test_de_json_no_optional(self, offline_bot):
json_dict = {
"level": self.level,
"rating": self.rating,
"current_level_rating": self.current_level_rating,
}
ur = UserRating.de_json(json_dict, offline_bot)
assert ur.api_kwargs == {}
assert ur.level == self.level
assert ur.rating == self.rating
assert ur.current_level_rating == self.current_level_rating
assert ur.next_level_rating is None
def test_to_dict(self, user_rating):
ur_dict = user_rating.to_dict()
assert isinstance(ur_dict, dict)
assert ur_dict["level"] == user_rating.level
assert ur_dict["rating"] == user_rating.rating
assert ur_dict["current_level_rating"] == user_rating.current_level_rating
assert ur_dict["next_level_rating"] == user_rating.next_level_rating
def test_equality(self):
a = UserRating(3, 200, 150, 300)
b = UserRating(3, 200, 100, None)
c = UserRating(3, 201, 150, 300)
d = UserRating(4, 200, 150, 300)
e = BotCommand("start", "description")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)