Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
Co-authored-by: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com>
This commit is contained in:
Bibo-Joshi 2024-05-20 15:25:25 +02:00 committed by GitHub
parent f3bd0f1462
commit 805b7bff32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 2742 additions and 171 deletions

View file

@ -14,9 +14,9 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-7.2-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.3-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
:alt: Supported Bot API version
.. image:: https://img.shields.io/pypi/dm/python-telegram-bot
:target: https://pypistats.org/packages/python-telegram-bot
@ -89,7 +89,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **7.2** are supported.
All types and methods of the Telegram Bot API **7.3** are supported.
Installing
==========

View file

@ -14,9 +14,9 @@
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-7.2-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.3-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
:alt: Supported Bot API version
.. image:: https://img.shields.io/pypi/dm/python-telegram-bot-raw
:target: https://pypistats.org/packages/python-telegram-bot-raw
@ -85,7 +85,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **7.2** are supported.
All types and methods of the Telegram Bot API **7.3** are supported.
Installing
==========

View file

@ -28,6 +28,16 @@ Available Types
telegram.callbackquery
telegram.chat
telegram.chatadministratorrights
telegram.chatbackground
telegram.backgroundtype
telegram.backgroundtypefill
telegram.backgroundtypewallpaper
telegram.backgroundtypepattern
telegram.backgroundtypechattheme
telegram.backgroundfill
telegram.backgroundfillsolid
telegram.backgroundfillgradient
telegram.backgroundfillfreeformgradient
telegram.chatboost
telegram.chatboostadded
telegram.chatboostremoved
@ -36,6 +46,7 @@ Available Types
telegram.chatboostsourcegiveaway
telegram.chatboostsourcepremium
telegram.chatboostupdated
telegram.chatfullinfo
telegram.chatinvitelink
telegram.chatjoinrequest
telegram.chatlocation
@ -77,6 +88,7 @@ Available Types
telegram.inputmediadocument
telegram.inputmediaphoto
telegram.inputmediavideo
telegram.inputpolloption
telegram.inputsticker
telegram.keyboardbutton
telegram.keyboardbuttonpolltype

View file

@ -0,0 +1,8 @@
BackgroundFill
==============
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.BackgroundFill
:members:
:show-inheritance:

View file

@ -0,0 +1,8 @@
BackgroundFillFreeformGradient
==============================
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.BackgroundFillFreeformGradient
:members:
:show-inheritance:

View file

@ -0,0 +1,8 @@
BackgroundFillGradient
======================
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.BackgroundFillGradient
:members:
:show-inheritance:

View file

@ -0,0 +1,8 @@
BackgroundFillSolid
===================
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.BackgroundFillSolid
:members:
:show-inheritance:

View file

@ -0,0 +1,8 @@
BackgroundType
==============
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.BackgroundType
:members:
:show-inheritance:

View file

@ -0,0 +1,8 @@
BackgroundTypeChatTheme
=======================
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.BackgroundTypeChatTheme
:members:
:show-inheritance:

View file

@ -0,0 +1,8 @@
BackgroundTypeFill
==================
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.BackgroundTypeFill
:members:
:show-inheritance:

View file

@ -0,0 +1,8 @@
BackgroundTypePattern
=====================
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.BackgroundTypePattern
:members:
:show-inheritance:

View file

@ -0,0 +1,8 @@
BackgroundTypeWallpaper
=======================
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.BackgroundTypeWallpaper
:members:
:show-inheritance:

View file

@ -0,0 +1,8 @@
ChatBackground
==============
.. versionadded:: NEXT.VERSION
.. autoclass:: telegram.ChatBackground
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
ChatFullInfo
============
.. autoclass:: telegram.ChatFullInfo
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
InputPollOption
===============
.. autoclass:: telegram.InputPollOption
:members:
:show-inheritance:

View file

@ -22,6 +22,15 @@ __author__ = "devs@python-telegram-bot.org"
__all__ = (
"Animation",
"Audio",
"BackgroundFill",
"BackgroundFillFreeformGradient",
"BackgroundFillGradient",
"BackgroundFillSolid",
"BackgroundType",
"BackgroundTypeChatTheme",
"BackgroundTypeFill",
"BackgroundTypePattern",
"BackgroundTypeWallpaper",
"Birthdate",
"Bot",
"BotCommand",
@ -46,6 +55,7 @@ __all__ = (
"CallbackQuery",
"Chat",
"ChatAdministratorRights",
"ChatBackground",
"ChatBoost",
"ChatBoostAdded",
"ChatBoostRemoved",
@ -54,6 +64,7 @@ __all__ = (
"ChatBoostSourceGiveaway",
"ChatBoostSourcePremium",
"ChatBoostUpdated",
"ChatFullInfo",
"ChatInviteLink",
"ChatJoinRequest",
"ChatLocation",
@ -131,6 +142,7 @@ __all__ = (
"InputMediaPhoto",
"InputMediaVideo",
"InputMessageContent",
"InputPollOption",
"InputSticker",
"InputTextMessageContent",
"InputVenueMessageContent",
@ -258,6 +270,18 @@ from ._business import (
from ._callbackquery import CallbackQuery
from ._chat import Chat
from ._chatadministratorrights import ChatAdministratorRights
from ._chatbackground import (
BackgroundFill,
BackgroundFillFreeformGradient,
BackgroundFillGradient,
BackgroundFillSolid,
BackgroundType,
BackgroundTypeChatTheme,
BackgroundTypeFill,
BackgroundTypePattern,
BackgroundTypeWallpaper,
ChatBackground,
)
from ._chatboost import (
ChatBoost,
ChatBoostAdded,
@ -269,6 +293,7 @@ from ._chatboost import (
ChatBoostUpdated,
UserChatBoosts,
)
from ._chatfullinfo import ChatFullInfo
from ._chatinvitelink import ChatInviteLink
from ._chatjoinrequest import ChatJoinRequest
from ._chatlocation import ChatLocation
@ -403,7 +428,7 @@ from ._payment.shippingaddress import ShippingAddress
from ._payment.shippingoption import ShippingOption
from ._payment.shippingquery import ShippingQuery
from ._payment.successfulpayment import SuccessfulPayment
from ._poll import Poll, PollAnswer, PollOption
from ._poll import InputPollOption, Poll, PollAnswer, PollOption
from ._proximityalerttriggered import ProximityAlertTriggered
from ._reaction import ReactionCount, ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji
from ._reply import ExternalReplyInfo, ReplyParameters, TextQuote

View file

@ -26,7 +26,7 @@ from telegram._utils.types import JSONDict
class Birthdate(TelegramObject):
"""
This object represents a user's birthday.
This object describes the birthdate of a user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`day`, and :attr:`month` are equal.

View file

@ -58,9 +58,9 @@ from telegram._botcommandscope import BotCommandScope
from telegram._botdescription import BotDescription, BotShortDescription
from telegram._botname import BotName
from telegram._business import BusinessConnection
from telegram._chat import Chat
from telegram._chatadministratorrights import ChatAdministratorRights
from telegram._chatboost import UserChatBoosts
from telegram._chatfullinfo import ChatFullInfo
from telegram._chatinvitelink import ChatInviteLink
from telegram._chatmember import ChatMember
from telegram._chatpermissions import ChatPermissions
@ -84,7 +84,7 @@ from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from telegram._menubutton import MenuButton
from telegram._message import Message
from telegram._messageid import MessageId
from telegram._poll import Poll
from telegram._poll import InputPollOption, Poll
from telegram._reaction import ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji
from telegram._reply import ReplyParameters
from telegram._sentwebappmessage import SentWebAppMessage
@ -2287,9 +2287,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"""
Use this method to send audio files, if you want Telegram clients to display the file
as a playable voice message. For this to work, your audio must be in an ``.ogg`` file
encoded with OPUS (other formats may be sent as Audio or Document). Bots can currently
send voice messages of up to :tg-const:`telegram.constants.FileSizeLimit.FILESIZE_UPLOAD`
in size, this limit may be changed in the future.
encoded with OPUS , or in .MP3 format, or in .M4A format (other formats may be sent as
:class:`~telegram.Audio` or :class:`~telegram.Document`). Bots can currently send voice
messages of up to :tg-const:`telegram.constants.FileSizeLimit.FILESIZE_UPLOAD` in size,
this limit may be changed in the future.
Note:
To use this method, the file must have the type :mimetype:`audio/ogg` and be no more
@ -2610,7 +2611,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
live_period (:obj:`int`, optional): Period in seconds for which the location will be
updated, should be between
:tg-const:`telegram.constants.LocationLimit.MIN_LIVE_PERIOD` and
:tg-const:`telegram.constants.LocationLimit.MAX_LIVE_PERIOD`.
:tg-const:`telegram.constants.LocationLimit.MAX_LIVE_PERIOD`, or
:tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` for live
locations that can be edited indefinitely.
heading (:obj:`int`, optional): For live locations, a direction in which the user is
moving, in degrees. Must be between
:tg-const:`telegram.constants.LocationLimit.MIN_HEADING` and
@ -2720,6 +2723,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
live_period: Optional[int] = None,
*,
location: Optional[Location] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2758,6 +2762,15 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
if specified.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for a new
inline keyboard.
live_period (:obj:`int`, optional): New period in seconds during which the location
can be updated, starting from the message send date. If
:tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` is specified,
then the location can be updated forever. Otherwise, the new value must not exceed
the current ``live_period`` by more than a day, and the live location expiration
date must remain within the next 90 days. If not specified, then ``live_period``
remains unchanged
.. versionadded:: NEXT.VERSION.
Keyword Args:
location (:class:`telegram.Location`, optional): The location to send.
@ -2790,6 +2803,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"horizontal_accuracy": horizontal_accuracy,
"heading": heading,
"proximity_alert_radius": proximity_alert_radius,
"live_period": live_period,
}
return await self._send_message(
@ -4434,16 +4448,19 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> Chat:
) -> ChatFullInfo:
"""
Use this method to get up to date information about the chat (current name of the user for
one-on-one conversations, current username of a user, group or channel, etc.).
.. versionchanged:: NEXT.VERSION
In accordance to Bot API 7.3, this method now returns a :class:`telegram.ChatFullInfo`.
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
Returns:
:class:`telegram.Chat`
:class:`telegram.ChatFullInfo`
Raises:
:class:`telegram.error.TelegramError`
@ -4461,7 +4478,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
return Chat.de_json(result, self) # type: ignore[return-value]
return ChatFullInfo.de_json(result, self) # type: ignore[return-value]
async def get_chat_administrators(
self,
@ -5311,7 +5328,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
edit stories posted by other users.
edit stories posted by other users, post stories to the chat page, pin chat
stories, and access the chat's story archive
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
@ -6798,7 +6816,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
self,
chat_id: Union[int, str],
question: str,
options: Sequence[str],
options: Sequence[Union[str, "InputPollOption"]],
is_anonymous: Optional[bool] = None,
type: Optional[str] = None, # pylint: disable=redefined-builtin
allows_multiple_answers: Optional[bool] = None,
@ -6815,6 +6833,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -6831,14 +6851,20 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
:tg-const:`telegram.Poll.MAX_QUESTION_LENGTH` characters.
options (Sequence[:obj:`str`]): Sequence of answer options,
options (Sequence[:obj:`str` | :class:`telegram.InputPollOption`]): Sequence of
:tg-const:`telegram.Poll.MIN_OPTION_NUMBER`-
:tg-const:`telegram.Poll.MAX_OPTION_NUMBER` strings
:tg-const:`telegram.Poll.MAX_OPTION_NUMBER` answer options. Each option may either
be a string with
:tg-const:`telegram.Poll.MIN_OPTION_LENGTH`-
:tg-const:`telegram.Poll.MAX_OPTION_LENGTH` characters each.
:tg-const:`telegram.Poll.MAX_OPTION_LENGTH` characters or an
:class:`~telegram.InputPollOption` object. Strings are converted to
:class:`~telegram.InputPollOption` objects automatically.
.. versionchanged:: 20.0
|sequenceargs|
.. versionchanged:: NEXT.VERSION
Bot API 7.3 adds support for :class:`~telegram.InputPollOption` objects.
is_anonymous (:obj:`bool`, optional): :obj:`True`, if the poll needs to be anonymous,
defaults to :obj:`True`.
type (:obj:`str`, optional): Poll type, :tg-const:`telegram.Poll.QUIZ` or
@ -6893,6 +6919,16 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: 21.1
question_parse_mode (:obj:`str`, optional): Mode for parsing entities in the question.
See the constants in :class:`telegram.constants.ParseMode` for the available modes.
Currently, only custom emoji entities are allowed.
.. versionadded:: NEXT.VERSION
question_entities (Sequence[:class:`telegram.Message`], optional): Special entities
that appear in the poll :paramref:`question`. It can be specified instead of
:paramref:`question_parse_mode`.
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -6924,7 +6960,10 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
data: JSONDict = {
"chat_id": chat_id,
"question": question,
"options": options,
"options": [
InputPollOption(option) if isinstance(option, str) else option
for option in options
],
"explanation_parse_mode": explanation_parse_mode,
"is_anonymous": is_anonymous,
"type": type,
@ -6935,6 +6974,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"explanation_entities": explanation_entities,
"open_period": open_period,
"close_date": close_date,
"question_parse_mode": question_parse_mode,
"question_entities": question_entities,
}
return await self._send_message(

View file

@ -189,7 +189,7 @@ class BusinessMessagesDeleted(TelegramObject):
class BusinessIntro(TelegramObject):
"""
This object represents the intro of a business account.
This object contains information about the start page settings of a Telegram Business account.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
@ -246,7 +246,7 @@ class BusinessIntro(TelegramObject):
class BusinessLocation(TelegramObject):
"""
This object represents the location of a business account.
This object contains information about the location of a Telegram Business account.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
@ -298,7 +298,7 @@ class BusinessLocation(TelegramObject):
class BusinessOpeningHoursInterval(TelegramObject):
"""
This object represents the time intervals describing business opening hours.
This object describes an interval of time during which a business is open.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
@ -390,7 +390,7 @@ class BusinessOpeningHoursInterval(TelegramObject):
class BusinessOpeningHours(TelegramObject):
"""
This object represents the opening hours of a business account.
This object describes the opening hours of a business.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their

View file

@ -461,6 +461,7 @@ class CallbackQuery(TelegramObject):
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
live_period: Optional[int] = None,
*,
location: Optional[Location] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -509,6 +510,7 @@ class CallbackQuery(TelegramObject):
horizontal_accuracy=horizontal_accuracy,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
live_period=live_period,
chat_id=None,
message_id=None,
)
@ -525,6 +527,7 @@ class CallbackQuery(TelegramObject):
horizontal_accuracy=horizontal_accuracy,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
live_period=live_period,
)
async def stop_message_live_location(

View file

@ -20,7 +20,7 @@
"""This module contains an object that represents a Telegram Chat."""
from datetime import datetime
from html import escape
from typing import TYPE_CHECKING, Final, Optional, Sequence, Tuple, Union
from typing import TYPE_CHECKING, Any, Final, Optional, Sequence, Tuple, Union
from telegram import constants
from telegram._birthdate import Birthdate
@ -36,9 +36,11 @@ from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.warnings import warn
from telegram.helpers import escape_markdown
from telegram.helpers import mention_html as helpers_mention_html
from telegram.helpers import mention_markdown as helpers_mention_markdown
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import (
@ -57,6 +59,7 @@ if TYPE_CHECKING:
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputPollOption,
LabeledPrice,
LinkPreviewOptions,
Location,
@ -74,6 +77,45 @@ if TYPE_CHECKING:
)
_deprecated_attrs = (
"accent_color_id",
"active_usernames",
"available_reactions",
"background_custom_emoji_id",
"bio",
"birthdate",
"business_intro",
"business_location",
"business_opening_hours",
"can_set_sticker_set",
"custom_emoji_sticker_set_name",
"description",
"emoji_status_custom_emoji_id",
"emoji_status_expiration_date",
"has_aggressive_anti_spam_enabled",
"has_hidden_members",
"has_private_forwards",
"has_protected_content",
"has_restricted_voice_and_video_messages",
"has_visible_history",
"invite_link",
"join_by_request",
"join_to_send_messages",
"linked_chat_id",
"location",
"message_auto_delete_time",
"permissions",
"personal_chat",
"photo",
"pinned_message",
"profile_accent_color_id",
"profile_background_custom_emoji_id",
"slow_mode_delay",
"sticker_set_name",
"unrestrict_boost_count",
)
class Chat(TelegramObject):
"""This object represents a chat.
@ -107,62 +149,134 @@ class Chat(TelegramObject):
last_name (:obj:`str`, optional): Last name of the other party in a private chat.
photo (:class:`telegram.ChatPhoto`, optional): Chat photo.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
bio (:obj:`str`, optional): Bio of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_private_forwards (:obj:`bool`, optional): :obj:`True`, if privacy settings of the other
party in the private chat allows to use ``tg://user?id=<user_id>`` links only in chats
with the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
description (:obj:`str`, optional): Description, for groups, supergroups and channel chats.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and
channel. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
pinned_message (:class:`telegram.Message`, optional): The most recent pinned message
(by sending date). Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
message_auto_delete_time (:obj:`int`, optional): The time after which all messages sent to
the chat will be automatically deleted; in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_protected_content (:obj:`bool`, optional): :obj:`True`, if messages from the chat can't
be forwarded to other chats. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_visible_history (:obj:`bool`, optional): :obj:`True`, if new chat members will have
access to old messages; available only to chat administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
can_set_sticker_set (:obj:`bool`, optional): :obj:`True`, if the bot can change group the
sticker set. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
linked_chat_id (:obj:`int`, optional): Unique identifier for the linked chat, i.e. the
discussion group identifier for a channel and vice versa; for supergroups and channel
chats. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
join_to_send_messages (:obj:`bool`, optional): :obj:`True`, if users need to join the
supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
join_by_request (:obj:`bool`, optional): :obj:`True`, if all users directly joining the
supergroup need to be approved by supergroup administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_restricted_voice_and_video_messages (:obj:`bool`, optional): :obj:`True`, if the
privacy settings of the other party restrict sending voice and video note messages
in the private chat. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
is_forum (:obj:`bool`, optional): :obj:`True`, if the supergroup chat is a forum
(has topics_ enabled).
@ -173,27 +287,47 @@ class Chat(TelegramObject):
only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_intro (:class:`telegram.BusinessIntro`, optional): For private chats with
business accounts, the intro of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_location (:class:`telegram.BusinessLocation`, optional): For private chats with
business accounts, the location of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_opening_hours (:class:`telegram.BusinessOpeningHours`, optional): For private
chats with business accounts, the opening hours of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
available_reactions (Sequence[:class:`telegram.ReactionType`], optional): List of available
reactions allowed in the chat. If omitted, then all of
:const:`telegram.constants.ReactionEmoji` are allowed. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
accent_color_id (:obj:`int`, optional): Identifier of the
:class:`accent color <telegram.constants.AccentColor>` for the chat name and
backgrounds of the chat photo, reply header, and link preview. See `accent colors`_
@ -201,62 +335,110 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji chosen
by the chat for the reply header and link preview background. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
profile_accent_color_id (:obj:`int`, optional): Identifier of the
:class:`accent color <telegram.constants.ProfileAccentColor>` for the chat's profile
background. See profile `accent colors`_ for more details. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
profile_background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of
the emoji chosen by the chat for its profile background. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
emoji_status_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji
status of the chat or the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
emoji_status_expiration_date (:class:`datetime.datetime`, optional): Expiration date of
emoji status of the chat or the other party in a private chat, in seconds. Returned
only in :meth:`telegram.Bot.get_chat`.
|datetime_localization|
.. versionadded:: 20.5
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_aggressive_anti_spam_enabled (:obj:`bool`, optional): :obj:`True`, if aggressive
anti-spam checks are enabled in the supergroup. The field is only available to chat
administrators. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_hidden_members (:obj:`bool`, optional): :obj:`True`, if non-administrators can only
get the list of bots and administrators in the chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
unrestrict_boost_count (:obj:`int`, optional): For supergroups, the minimum number of
boosts that a non-administrator user needs to add in order to ignore slow mode and chat
permissions. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
custom_emoji_sticker_set_name (:obj:`str`, optional): For supergroups, the name of the
group's custom emoji sticker set. Custom emoji from this set can be used by all users
and bots in the group. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
birthdate (:obj:`telegram.Birthdate`, optional): For private chats,
the date of birth of the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
personal_chat (:obj:`telegram.Chat`, optional): For private chats, the personal channel of
the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
Attributes:
id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits
and some programming languages may have difficulty/silent defects in interpreting it.
@ -271,62 +453,134 @@ class Chat(TelegramObject):
last_name (:obj:`str`): Optional. Last name of the other party in a private chat.
photo (:class:`telegram.ChatPhoto`): Optional. Chat photo.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_private_forwards (:obj:`bool`): Optional. :obj:`True`, if privacy settings of the other
party in the private chat allows to use ``tg://user?id=<user_id>`` links only in chats
with the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and
channel. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
pinned_message (:class:`telegram.Message`): Optional. The most recent pinned message
(by sending date). Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user. Returned only in
:meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
message_auto_delete_time (:obj:`int`): Optional. The time after which all messages sent to
the chat will be automatically deleted; in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if messages from the chat can't
be forwarded to other chats. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_visible_history (:obj:`bool`): Optional. :obj:`True`, if new chat members will have
access to old messages; available only to chat administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the
sticker set. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
linked_chat_id (:obj:`int`): Optional. Unique identifier for the linked chat, i.e. the
discussion group identifier for a channel and vice versa; for supergroups and channel
chats. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
join_to_send_messages (:obj:`bool`): Optional. :obj:`True`, if users need to join
the supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
join_by_request (:obj:`bool`): Optional. :obj:`True`, if all users directly
joining the supergroup need to be approved by supergroup administrators. Returned only
in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_restricted_voice_and_video_messages (:obj:`bool`): Optional. :obj:`True`, if the
privacy settings of the other party restrict sending voice and video note messages
in the private chat. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
is_forum (:obj:`bool`): Optional. :obj:`True`, if the supergroup chat is a forum
(has topics_ enabled).
@ -339,27 +593,47 @@ class Chat(TelegramObject):
obtained via :meth:`~telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with
business accounts, the intro of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with
business accounts, the location of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private
chats with business accounts, the opening hours of the business. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
available_reactions (Tuple[:class:`telegram.ReactionType`]): Optional. List of available
reactions allowed in the chat. If omitted, then all of
:const:`telegram.constants.ReactionEmoji` are allowed. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
accent_color_id (:obj:`int`): Optional. Identifier of the
:class:`accent color <telegram.constants.AccentColor>` for the chat name and
backgrounds of the chat photo, reply header, and link preview. See `accent colors`_
@ -367,62 +641,110 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji chosen
by the chat for the reply header and link preview background. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
profile_accent_color_id (:obj:`int`): Optional. Identifier of the
:class:`accent color <telegram.constants.ProfileAccentColor>` for the chat's profile
background. See profile `accent colors`_ for more details. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
profile_background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of
the emoji chosen by the chat for its profile background. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.8
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
emoji_status_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji
status of the chat or the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
emoji_status_expiration_date (:class:`datetime.datetime`): Optional. Expiration date of
emoji status of the chat or the other party in a private chat, in seconds. Returned
only in :meth:`telegram.Bot.get_chat`.
|datetime_localization|
.. versionadded:: 20.5
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_aggressive_anti_spam_enabled (:obj:`bool`): Optional. :obj:`True`, if aggressive
anti-spam checks are enabled in the supergroup. The field is only available to chat
administrators. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
has_hidden_members (:obj:`bool`): Optional. :obj:`True`, if non-administrators can only
get the list of bots and administrators in the chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
unrestrict_boost_count (:obj:`int`): Optional. For supergroups, the minimum number of
boosts that a non-administrator user needs to add in order to ignore slow mode and chat
permissions. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
custom_emoji_sticker_set_name (:obj:`str`): Optional. For supergroups, the name of the
group's custom emoji sticker set. Custom emoji from this set can be used by all users
and bots in the group. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
birthdate (:obj:`telegram.Birthdate`): Optional. For private chats,
the date of birth of the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
personal_chat (:obj:`telegram.Chat`): Optional. For private chats, the personal channel of
the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.1
.. deprecated:: NEXT.VERSION
In accordance to Bot API 7.3, this attribute will be moved to
:class:`telegram.ChatFullInfo`.
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
.. _accent colors: https://core.telegram.org/bots/api#accent-colors
"""
@ -471,7 +793,6 @@ class Chat(TelegramObject):
"unrestrict_boost_count",
"username",
)
SENDER: Final[str] = constants.ChatType.SENDER
""":const:`telegram.constants.ChatType.SENDER`
@ -518,7 +839,7 @@ class Chat(TelegramObject):
has_aggressive_anti_spam_enabled: Optional[bool] = None,
has_hidden_members: Optional[bool] = None,
available_reactions: Optional[Sequence[ReactionType]] = None,
accent_color_id: Optional[int] = None,
accent_color_id: Optional[int] = None, # required in API 7.3 - Optional for back compat
background_custom_emoji_id: Optional[str] = None,
profile_accent_color_id: Optional[int] = None,
profile_background_custom_emoji_id: Optional[str] = None,
@ -585,10 +906,30 @@ class Chat(TelegramObject):
self.business_location: Optional["BusinessLocation"] = business_location
self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours
if self.__class__ is Chat:
for arg in _deprecated_attrs:
if (val := object.__getattribute__(self, arg)) is not None and val != ():
warn(
f"The argument `{arg}` is deprecated and will only be available via "
"`ChatFullInfo` in the future.",
stacklevel=2,
category=PTBDeprecationWarning,
)
self._id_attrs = (self.id,)
self._freeze()
def __getattribute__(self, name: str) -> Any:
if name in _deprecated_attrs and self.__class__ is Chat:
warn(
f"The attribute `{name}` is deprecated and will only be accessible via "
"`ChatFullInfo` in the future.",
stacklevel=2,
category=PTBDeprecationWarning,
)
return super().__getattribute__(name)
@property
def effective_name(self) -> Optional[str]:
"""
@ -658,7 +999,7 @@ class Chat(TelegramObject):
data["location"] = ChatLocation.de_json(data.get("location"), bot)
data["available_reactions"] = ReactionType.de_list(data.get("available_reactions"), bot)
data["birthdate"] = Birthdate.de_json(data.get("birthdate"), bot)
data["personal_chat"] = cls.de_json(data.get("personal_chat"), bot)
data["personal_chat"] = Chat.de_json(data.get("personal_chat"), bot)
data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot)
data["business_location"] = BusinessLocation.de_json(data.get("business_location"), bot)
data["business_opening_hours"] = BusinessOpeningHours.de_json(
@ -2545,7 +2886,7 @@ class Chat(TelegramObject):
async def send_poll(
self,
question: str,
options: Sequence[str],
options: Sequence[Union[str, "InputPollOption"]],
is_anonymous: Optional[bool] = None,
type: Optional[str] = None,
allows_multiple_answers: Optional[bool] = None,
@ -2562,6 +2903,8 @@ class Chat(TelegramObject):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2608,6 +2951,8 @@ class Chat(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
question_parse_mode=question_parse_mode,
question_entities=question_entities,
)
async def send_copy(

View file

@ -80,8 +80,9 @@ class ChatAdministratorRights(TelegramObject):
.. versionadded:: 20.6
.. versionchanged:: 21.0
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit stories posted
by other users, post stories to the chat page, pin chat stories, and access the chat's
story archive
.. versionadded:: 20.6
.. versionchanged:: 21.0
@ -128,8 +129,9 @@ class ChatAdministratorRights(TelegramObject):
.. versionadded:: 20.6
.. versionchanged:: 21.0
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit stories posted
by other users, post stories to the chat page, pin chat stories, and access the chat's
story archive
.. versionadded:: 20.6
.. versionchanged:: 21.0

540
telegram/_chatbackground.py Normal file
View file

@ -0,0 +1,540 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 objects related to chat backgrounds."""
from typing import TYPE_CHECKING, Dict, Final, Optional, Sequence, Tuple, Type
from telegram import constants
from telegram._files.document import Document
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class BackgroundFill(TelegramObject):
"""Base class for Telegram BackgroundFill Objects. It can be one of:
* :class:`telegram.BackgroundFillSolid`
* :class:`telegram.BackgroundFillGradient`
* :class:`telegram.BackgroundFillFreeformGradient`
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: NEXT.VERSION
Args:
type (:obj:`str`): Type of the background fill. Can be one of:
:attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT`
or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`.
Attributes:
type (:obj:`str`): Type of the background fill. Can be one of:
:attr:`~telegram.BackgroundFill.SOLID`, :attr:`~telegram.BackgroundFill.GRADIENT`
or :attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`.
"""
__slots__ = ("type",)
SOLID: Final[constants.BackgroundFillType] = constants.BackgroundFillType.SOLID
""":const:`telegram.constants.BackgroundFillType.SOLID`"""
GRADIENT: Final[constants.BackgroundFillType] = constants.BackgroundFillType.GRADIENT
""":const:`telegram.constants.BackgroundFillType.GRADIENT`"""
FREEFORM_GRADIENT: Final[constants.BackgroundFillType] = (
constants.BackgroundFillType.FREEFORM_GRADIENT
)
""":const:`telegram.constants.BackgroundFillType.FREEFORM_GRADIENT`"""
def __init__(
self,
type: str, # pylint: disable=redefined-builtin
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required by all subclasses
self.type: str = enum.get_member(constants.BackgroundFillType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BackgroundFill"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: Dict[str, Type[BackgroundFill]] = {
cls.SOLID: BackgroundFillSolid,
cls.GRADIENT: BackgroundFillGradient,
cls.FREEFORM_GRADIENT: BackgroundFillFreeformGradient,
}
if cls is BackgroundFill and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
return super().de_json(data=data, bot=bot)
class BackgroundFillSolid(BackgroundFill):
"""
The background is filled using the selected color.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`color` is equal.
.. versionadded:: NEXT.VERSION
Args:
color (:obj:`int`): The color of the background fill in the `RGB24` format.
Attributes:
type (:obj:`str`): Type of the background fill. Always
:attr:`~telegram.BackgroundFill.SOLID`.
color (:obj:`int`): The color of the background fill in the `RGB24` format.
"""
__slots__ = ("color",)
def __init__(
self,
color: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.SOLID, api_kwargs=api_kwargs)
with self._unfrozen():
self.color: int = color
self._id_attrs = (self.color,)
class BackgroundFillGradient(BackgroundFill):
"""
The background is a gradient fill.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`top_color`, :attr:`bottom_color`
and :attr:`rotation_angle` are equal.
.. versionadded:: NEXT.VERSION
Args:
top_color (:obj:`int`): Top color of the gradient in the `RGB24` format.
bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format.
rotation_angle (:obj:`int`): Clockwise rotation angle of the background
fill in degrees;
0-:tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`.
Attributes:
type (:obj:`str`): Type of the background fill. Always
:attr:`~telegram.BackgroundFill.GRADIENT`.
top_color (:obj:`int`): Top color of the gradient in the `RGB24` format.
bottom_color (:obj:`int`): Bottom color of the gradient in the `RGB24` format.
rotation_angle (:obj:`int`): Clockwise rotation angle of the background
fill in degrees;
0-:tg-const:`telegram.constants.BackgroundFillLimit.MAX_ROTATION_ANGLE`.
"""
__slots__ = ("bottom_color", "rotation_angle", "top_color")
def __init__(
self,
top_color: int,
bottom_color: int,
rotation_angle: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.GRADIENT, api_kwargs=api_kwargs)
with self._unfrozen():
self.top_color: int = top_color
self.bottom_color: int = bottom_color
self.rotation_angle: int = rotation_angle
self._id_attrs = (self.top_color, self.bottom_color, self.rotation_angle)
class BackgroundFillFreeformGradient(BackgroundFill):
"""
The background is a freeform gradient that rotates after every message in the chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`colors` is equal.
.. versionadded:: NEXT.VERSION
Args:
colors (Sequence[:obj:`int`]): A list of the 3 or 4 base colors that are used to
generate the freeform gradient in the `RGB24` format
Attributes:
type (:obj:`str`): Type of the background fill. Always
:attr:`~telegram.BackgroundFill.FREEFORM_GRADIENT`.
colors (Sequence[:obj:`int`]): A list of the 3 or 4 base colors that are used to
generate the freeform gradient in the `RGB24` format
"""
__slots__ = ("colors",)
def __init__(
self,
colors: Sequence[int],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.FREEFORM_GRADIENT, api_kwargs=api_kwargs)
with self._unfrozen():
self.colors: Tuple[int, ...] = parse_sequence_arg(colors)
self._id_attrs = (self.colors,)
class BackgroundType(TelegramObject):
"""Base class for Telegram BackgroundType Objects. It can be one of:
* :class:`telegram.BackgroundTypeFill`
* :class:`telegram.BackgroundTypeWallpaper`
* :class:`telegram.BackgroundTypePattern`
* :class:`telegram.BackgroundTypeChatTheme`.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: NEXT.VERSION
Args:
type (:obj:`str`): Type of the background. Can be one of:
:attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER`
:attr:`~telegram.BackgroundType.PATTERN` or
:attr:`~telegram.BackgroundType.CHAT_THEME`.
Attributes:
type (:obj:`str`): Type of the background. Can be one of:
:attr:`~telegram.BackgroundType.FILL`, :attr:`~telegram.BackgroundType.WALLPAPER`
:attr:`~telegram.BackgroundType.PATTERN` or
:attr:`~telegram.BackgroundType.CHAT_THEME`.
"""
__slots__ = ("type",)
FILL: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.FILL
""":const:`telegram.constants.BackgroundTypeType.FILL`"""
WALLPAPER: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.WALLPAPER
""":const:`telegram.constants.BackgroundTypeType.WALLPAPER`"""
PATTERN: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.PATTERN
""":const:`telegram.constants.BackgroundTypeType.PATTERN`"""
CHAT_THEME: Final[constants.BackgroundTypeType] = constants.BackgroundTypeType.CHAT_THEME
""":const:`telegram.constants.BackgroundTypeType.CHAT_THEME`"""
def __init__(
self,
type: str, # pylint: disable=redefined-builtin
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required by all subclasses
self.type: str = enum.get_member(constants.BackgroundTypeType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BackgroundType"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: Dict[str, Type[BackgroundType]] = {
cls.FILL: BackgroundTypeFill,
cls.WALLPAPER: BackgroundTypeWallpaper,
cls.PATTERN: BackgroundTypePattern,
cls.CHAT_THEME: BackgroundTypeChatTheme,
}
if cls is BackgroundType and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
if "fill" in data:
data["fill"] = BackgroundFill.de_json(data.get("fill"), bot)
if "document" in data:
data["document"] = Document.de_json(data.get("document"), bot)
return super().de_json(data=data, bot=bot)
class BackgroundTypeFill(BackgroundType):
"""
The background is automatically filled based on the selected colors.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`fill` and :attr:`dark_theme_dimming` are equal.
.. versionadded:: NEXT.VERSION
Args:
fill (:obj:`telegram.BackgroundFill`): The background fill.
dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a
percentage;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`.
Attributes:
type (:obj:`str`): Type of the background. Always
:attr:`~telegram.BackgroundType.FILL`.
fill (:obj:`telegram.BackgroundFill`): The background fill.
dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a
percentage;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`.
"""
__slots__ = ("dark_theme_dimming", "fill")
def __init__(
self,
fill: BackgroundFill,
dark_theme_dimming: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.FILL, api_kwargs=api_kwargs)
with self._unfrozen():
self.fill: BackgroundFill = fill
self.dark_theme_dimming: int = dark_theme_dimming
self._id_attrs = (self.fill, self.dark_theme_dimming)
class BackgroundTypeWallpaper(BackgroundType):
"""
The background is a wallpaper in the `JPEG` format.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`document` and :attr:`dark_theme_dimming` are equal.
.. versionadded:: NEXT.VERSION
Args:
document (:obj:`telegram.Document`): Document with the wallpaper
dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a
percentage;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`.
is_blurred (:obj:`bool`, optional): :obj:`True`, if the wallpaper is downscaled to fit
in a 450x450 square and then box-blurred with radius 12
is_moving (:obj:`bool`, optional): :obj:`True`, if the background moves slightly
when the device is tilted
Attributes:
type (:obj:`str`): Type of the background. Always
:attr:`~telegram.BackgroundType.WALLPAPER`.
document (:obj:`telegram.Document`): Document with the wallpaper
dark_theme_dimming (:obj:`int`): Dimming of the background in dark themes, as a
percentage;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_DIMMING`.
is_blurred (:obj:`bool`): Optional. :obj:`True`, if the wallpaper is downscaled to fit
in a 450x450 square and then box-blurred with radius 12
is_moving (:obj:`bool`): Optional. :obj:`True`, if the background moves slightly
when the device is tilted
"""
__slots__ = ("dark_theme_dimming", "document", "is_blurred", "is_moving")
def __init__(
self,
document: Document,
dark_theme_dimming: int,
is_blurred: Optional[bool] = None,
is_moving: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.WALLPAPER, api_kwargs=api_kwargs)
with self._unfrozen():
# Required
self.document: Document = document
self.dark_theme_dimming: int = dark_theme_dimming
# Optionals
self.is_blurred: Optional[bool] = is_blurred
self.is_moving: Optional[bool] = is_moving
self._id_attrs = (self.document, self.dark_theme_dimming)
class BackgroundTypePattern(BackgroundType):
"""
The background is a `PNG` or `TGV` (gzipped subset of `SVG` with `MIME` type
`"application/x-tgwallpattern"`) pattern to be combined with the background fill
chosen by the user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`document` and :attr:`fill` and :attr:`intensity` are equal.
.. versionadded:: NEXT.VERSION
Args:
document (:obj:`telegram.Document`): Document with the pattern.
fill (:obj:`telegram.BackgroundFill`): The background fill that is combined with
the pattern.
intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled
background;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`.
is_inverted (:obj:`int`, optional): :obj:`True`, if the background fill must be applied
only to the pattern itself. All other pixels are black in this case. For dark
themes only.
is_moving (:obj:`bool`, optional): :obj:`True`, if the background moves slightly
when the device is tilted.
Attributes:
type (:obj:`str`): Type of the background. Always
:attr:`~telegram.BackgroundType.PATTERN`.
document (:obj:`telegram.Document`): Document with the pattern.
fill (:obj:`telegram.BackgroundFill`): The background fill that is combined with
the pattern.
intensity (:obj:`int`): Intensity of the pattern when it is shown above the filled
background;
0-:tg-const:`telegram.constants.BackgroundTypeLimit.MAX_INTENSITY`.
is_inverted (:obj:`int`): Optional. :obj:`True`, if the background fill must be applied
only to the pattern itself. All other pixels are black in this case. For dark
themes only.
is_moving (:obj:`bool`): Optional. :obj:`True`, if the background moves slightly
when the device is tilted.
"""
__slots__ = (
"document",
"fill",
"intensity",
"is_inverted",
"is_moving",
)
def __init__(
self,
document: Document,
fill: BackgroundFill,
intensity: int,
is_inverted: Optional[bool] = None,
is_moving: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.PATTERN, api_kwargs=api_kwargs)
with self._unfrozen():
# Required
self.document: Document = document
self.fill: BackgroundFill = fill
self.intensity: int = intensity
# Optionals
self.is_inverted: Optional[bool] = is_inverted
self.is_moving: Optional[bool] = is_moving
self._id_attrs = (self.document, self.fill, self.intensity)
class BackgroundTypeChatTheme(BackgroundType):
"""
The background is taken directly from a built-in chat theme.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`theme_name` is equal.
.. versionadded:: NEXT.VERSION
Args:
theme_name (:obj:`str`): Name of the chat theme, which is usually an emoji.
Attributes:
type (:obj:`str`): Type of the background. Always
:attr:`~telegram.BackgroundType.CHAT_THEME`.
theme_name (:obj:`str`): Name of the chat theme, which is usually an emoji.
"""
__slots__ = ("theme_name",)
def __init__(
self,
theme_name: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=self.CHAT_THEME, api_kwargs=api_kwargs)
with self._unfrozen():
self.theme_name: str = theme_name
self._id_attrs = (self.theme_name,)
class ChatBackground(TelegramObject):
"""
This object represents a chat background.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: NEXT.VERSION
Args:
type (:obj:`telegram.BackgroundType`): Type of the background.
Attributes:
type (:obj:`telegram.BackgroundType`): Type of the background.
"""
__slots__ = ("type",)
def __init__(
self,
type: BackgroundType, # pylint: disable=redefined-builtin
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.type: BackgroundType = type
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatBackground"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["type"] = BackgroundType.de_json(data.get("type"), bot)
return super().de_json(data=data, bot=bot)

166
telegram/_chatfullinfo.py Normal file
View file

@ -0,0 +1,166 @@
#!/usr/bin/env python
# pylint: disable=redefined-builtin
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 ChatFullInfo."""
from datetime import datetime
from typing import TYPE_CHECKING, Optional, Sequence
from telegram._birthdate import Birthdate
from telegram._chat import Chat
from telegram._chatlocation import ChatLocation
from telegram._chatpermissions import ChatPermissions
from telegram._files.chatphoto import ChatPhoto
from telegram._reaction import ReactionType
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import BusinessIntro, BusinessLocation, BusinessOpeningHours, Message
class ChatFullInfo(Chat):
"""
This object contains full information about a chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.Chat.id` is equal.
Caution:
This class is a subclass of :class:`telegram.Chat` and inherits all attributes and methods
for backwards compatibility. In the future, this class will *NOT* inherit from
:class:`telegram.Chat`.
.. seealso::
All arguments and attributes can be found in :class:`telegram.Chat`.
.. versionadded:: NEXT.VERSION
Args:
max_reaction_count (:obj:`int`): The maximum number of reactions that can be set on a
message in the chat.
.. versionadded:: NEXT.VERSION
Attributes:
max_reaction_count (:obj:`int`): The maximum number of reactions that can be set on a
message in the chat.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("max_reaction_count",)
def __init__(
self,
id: int,
type: str,
accent_color_id: int, # API 7.3 made this argument required
max_reaction_count: int, # NEW arg in api 7.3 and is required
title: Optional[str] = None,
username: Optional[str] = None,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
is_forum: Optional[bool] = None,
photo: Optional[ChatPhoto] = None,
active_usernames: Optional[Sequence[str]] = None,
birthdate: Optional[Birthdate] = None,
business_intro: Optional["BusinessIntro"] = None,
business_location: Optional["BusinessLocation"] = None,
business_opening_hours: Optional["BusinessOpeningHours"] = None,
personal_chat: Optional["Chat"] = None,
available_reactions: Optional[Sequence[ReactionType]] = None,
background_custom_emoji_id: Optional[str] = None,
profile_accent_color_id: Optional[int] = None,
profile_background_custom_emoji_id: Optional[str] = None,
emoji_status_custom_emoji_id: Optional[str] = None,
emoji_status_expiration_date: Optional[datetime] = None,
bio: Optional[str] = None,
has_private_forwards: Optional[bool] = None,
has_restricted_voice_and_video_messages: Optional[bool] = None,
join_to_send_messages: Optional[bool] = None,
join_by_request: Optional[bool] = None,
description: Optional[str] = None,
invite_link: Optional[str] = None,
pinned_message: Optional["Message"] = None,
permissions: Optional[ChatPermissions] = None,
slow_mode_delay: Optional[int] = None,
unrestrict_boost_count: Optional[int] = None,
message_auto_delete_time: Optional[int] = None,
has_aggressive_anti_spam_enabled: Optional[bool] = None,
has_hidden_members: Optional[bool] = None,
has_protected_content: Optional[bool] = None,
has_visible_history: Optional[bool] = None,
sticker_set_name: Optional[str] = None,
can_set_sticker_set: Optional[bool] = None,
custom_emoji_sticker_set_name: Optional[str] = None,
linked_chat_id: Optional[int] = None,
location: Optional[ChatLocation] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(
id=id,
type=type,
title=title,
username=username,
first_name=first_name,
last_name=last_name,
photo=photo,
description=description,
invite_link=invite_link,
pinned_message=pinned_message,
permissions=permissions,
sticker_set_name=sticker_set_name,
can_set_sticker_set=can_set_sticker_set,
slow_mode_delay=slow_mode_delay,
bio=bio,
linked_chat_id=linked_chat_id,
location=location,
message_auto_delete_time=message_auto_delete_time,
has_private_forwards=has_private_forwards,
has_protected_content=has_protected_content,
join_to_send_messages=join_to_send_messages,
join_by_request=join_by_request,
has_restricted_voice_and_video_messages=has_restricted_voice_and_video_messages,
is_forum=is_forum,
active_usernames=active_usernames,
emoji_status_custom_emoji_id=emoji_status_custom_emoji_id,
emoji_status_expiration_date=emoji_status_expiration_date,
has_aggressive_anti_spam_enabled=has_aggressive_anti_spam_enabled,
has_hidden_members=has_hidden_members,
available_reactions=available_reactions,
accent_color_id=accent_color_id,
background_custom_emoji_id=background_custom_emoji_id,
profile_accent_color_id=profile_accent_color_id,
profile_background_custom_emoji_id=profile_background_custom_emoji_id,
has_visible_history=has_visible_history,
unrestrict_boost_count=unrestrict_boost_count,
custom_emoji_sticker_set_name=custom_emoji_sticker_set_name,
birthdate=birthdate,
personal_chat=personal_chat,
business_intro=business_intro,
business_location=business_location,
business_opening_hours=business_opening_hours,
api_kwargs=api_kwargs,
)
# Required and unique to this class-
with self._unfrozen():
self.max_reaction_count: int = max_reaction_count
self._freeze()

View file

@ -235,8 +235,9 @@ class ChatMemberAdministrator(ChatMember):
.. versionadded:: 20.6
.. versionchanged:: 21.0
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit stories posted
by other users, post stories to the chat page, pin chat stories, and access the chat's
story archive
.. versionadded:: 20.6
.. versionchanged:: 21.0
@ -294,8 +295,9 @@ class ChatMemberAdministrator(ChatMember):
.. versionadded:: 20.6
.. versionchanged:: 21.0
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit stories posted
by other users, post stories to the chat page, pin chat stories, and access the chat's
story archive
.. versionadded:: 20.6
.. versionchanged:: 21.0

View file

@ -63,6 +63,11 @@ class ChatMemberUpdated(TelegramObject):
chat via a chat folder invite link
.. versionadded:: 20.3
via_join_request (:obj:`bool`, optional): :obj:`True`, if the user joined the chat after
sending a direct join request without using an invite link and being approved by
an administrator
.. versionadded:: NEXT.VERSION
Attributes:
chat (:class:`telegram.Chat`): Chat the user belongs to.
@ -80,6 +85,11 @@ class ChatMemberUpdated(TelegramObject):
chat via a chat folder invite link
.. versionadded:: 20.3
via_join_request (:obj:`bool`): Optional. :obj:`True`, if the user joined the chat after
sending a direct join request without using an invite link and being approved
by an administrator
.. versionadded:: NEXT.VERSION
"""
@ -91,6 +101,7 @@ class ChatMemberUpdated(TelegramObject):
"new_chat_member",
"old_chat_member",
"via_chat_folder_invite_link",
"via_join_request",
)
def __init__(
@ -102,6 +113,7 @@ class ChatMemberUpdated(TelegramObject):
new_chat_member: ChatMember,
invite_link: Optional[ChatInviteLink] = None,
via_chat_folder_invite_link: Optional[bool] = None,
via_join_request: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -116,6 +128,7 @@ class ChatMemberUpdated(TelegramObject):
# Optionals
self.invite_link: Optional[ChatInviteLink] = invite_link
self.via_join_request: Optional[bool] = via_join_request
self._id_attrs = (
self.chat,

View file

@ -30,7 +30,8 @@ class ForceReply(TelegramObject):
Upon receiving a message with this object, Telegram clients will display a reply interface to
the user (act as if the user has selected the bot's message and tapped 'Reply'). This can be
extremely useful if you want to create user-friendly step-by-step interfaces without having
to sacrifice privacy mode.
to sacrifice `privacy mode <https://core.telegram.org/bots/features#privacy-mode>`_. Not
supported in channels and for messages sent on behalf of a Telegram Business account.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`selective` is equal.

View file

@ -91,6 +91,7 @@ class InlineKeyboardButton(TelegramObject):
to the bot when button is pressed, UTF-8
:tg-const:`telegram.InlineKeyboardButton.MIN_CALLBACK_DATA`-
:tg-const:`telegram.InlineKeyboardButton.MAX_CALLBACK_DATA` bytes.
Not supported for messages sent on behalf of a Telegram Business account.
If the bot instance allows arbitrary callback data, anything can be passed.
Tip:
@ -102,25 +103,25 @@ class InlineKeyboardButton(TelegramObject):
<https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses
the button. The Web App will be able to send an arbitrary message on behalf of the user
using the method :meth:`~telegram.Bot.answer_web_app_query`. Available only in
private chats between a user and the bot.
private chats between a user and the bot. Not supported for messages sent on behalf of
a Telegram Business account.
.. versionadded:: 20.0
switch_inline_query (:obj:`str`, optional): If set, pressing the button will prompt the
user to select one of their chats, open that chat and insert the bot's username and the
specified inline query in the input field. Can be empty, in which case just the bot's
username will be inserted. This offers an easy way for users to start using your bot
in inline mode when they are currently in a private chat with it. Especially useful
when combined with ``switch_pm*`` actions - in this case the user will be automatically
returned to the chat they switched from, skipping the chat selection screen.
switch_inline_query (:obj:`str`, optional): If set, pressing the button will insert the
bot's username and the specified inline query in the current chat's input field. May be
empty, in which case only the bot's username will be inserted.
This offers a quick way for the user to open your bot in inline mode in the same chat -
good for selecting something from multiple options. Not supported in channels and for
messages sent on behalf of a Telegram Business account.
Tip:
This is similar to the new parameter :paramref:`switch_inline_query_chosen_chat`,
but gives no control over which chats can be selected.
switch_inline_query_current_chat (:obj:`str`, optional): If set, pressing the button will
insert the bot's username and the specified inline query in the current chat's input
field. Can be empty, in which case only the bot's username will be inserted. This
offers a quick way for the user to open your bot in inline mode in the same chat - good
for selecting something from multiple options.
prompt the user to select one of their chats of the specified type, open that chat and
insert the bot's username and the specified inline query in the input field. Not
supported for messages sent on behalf of a Telegram Business account.
callback_game (:class:`telegram.CallbackGame`, optional): Description of the game that will
be launched when the user presses the button. This type of button **must** always be
the **first** button in the first row.
@ -130,7 +131,8 @@ class InlineKeyboardButton(TelegramObject):
switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`, optional):
If set, pressing the button will prompt the user to select one of their chats of the
specified type, open that chat and insert the bot's username and the specified inline
query in the input field.
query in the input field. Not supported for messages sent on behalf of a Telegram
Business account.
.. versionadded:: 20.3
@ -159,29 +161,30 @@ class InlineKeyboardButton(TelegramObject):
to the bot when button is pressed, UTF-8
:tg-const:`telegram.InlineKeyboardButton.MIN_CALLBACK_DATA`-
:tg-const:`telegram.InlineKeyboardButton.MAX_CALLBACK_DATA` bytes.
Not supported for messages sent on behalf of a Telegram Business account.
web_app (:obj:`telegram.WebAppInfo`): Optional. Description of the `Web App
<https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses
the button. The Web App will be able to send an arbitrary message on behalf of the user
using the method :meth:`~telegram.Bot.answer_web_app_query`. Available only in
private chats between a user and the bot.
private chats between a user and the bot. Not supported for messages sent on behalf of
a Telegram Business account.
.. versionadded:: 20.0
switch_inline_query (:obj:`str`): Optional. If set, pressing the button will prompt the
user to select one of their chats, open that chat and insert the bot's username and the
specified inline query in the input field. Can be empty, in which case just the bot's
username will be inserted. This offers an easy way for users to start using your bot
in inline mode when they are currently in a private chat with it. Especially useful
when combined with ``switch_pm*`` actions - in this case the user will be automatically
returned to the chat they switched from, skipping the chat selection screen.
switch_inline_query (:obj:`str`): Optional. If set, pressing the button will insert the
bot's username and the specified inline query in the current chat's input field. May be
empty, in which case only the bot's username will be inserted.
This offers a quick way for the user to open your bot in inline mode in the same chat -
good for selecting something from multiple options. Not supported in channels and for
messages sent on behalf of a Telegram Business account.
Tip:
This is similar to the new parameter :paramref:`switch_inline_query_chosen_chat`,
but gives no control over which chats can be selected.
switch_inline_query_current_chat (:obj:`str`): Optional. If set, pressing the button will
insert the bot's username and the specified inline query in the current chat's input
field. Can be empty, in which case only the bot's username will be inserted. This
offers a quick way for the user to open your bot in inline mode in the same chat - good
for selecting something from multiple options.
prompt the user to select one of their chats of the specified type, open that chat and
insert the bot's username and the specified inline query in the input field. Not
supported for messages sent on behalf of a Telegram Business account.
callback_game (:class:`telegram.CallbackGame`): Optional. Description of the game that will
be launched when the user presses the button. This type of button **must** always be
the **first** button in the first row.
@ -191,7 +194,8 @@ class InlineKeyboardButton(TelegramObject):
switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`): Optional.
If set, pressing the button will prompt the user to select one of their chats of the
specified type, open that chat and insert the bot's username and the specified inline
query in the input field.
query in the input field. Not supported for messages sent on behalf of a Telegram
Business account.
.. versionadded:: 20.3

View file

@ -89,7 +89,9 @@ class InlineQueryResultLocation(InlineQueryResult):
live_period (:obj:`int`): Optional. Period in seconds for which the location will be
updated, should be between
:tg-const:`telegram.InlineQueryResultLocation.MIN_LIVE_PERIOD` and
:tg-const:`telegram.InlineQueryResultLocation.MAX_LIVE_PERIOD`.
:tg-const:`telegram.InlineQueryResultLocation.MAX_LIVE_PERIOD` or
:tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` for live
locations that can be edited indefinitely.
heading (:obj:`int`): Optional. For live locations, a direction in which the user is
moving, in degrees. Must be between
:tg-const:`telegram.InlineQueryResultLocation.MIN_HEADING` and

View file

@ -42,7 +42,9 @@ class InputLocationMessageContent(InputMessageContent):
live_period (:obj:`int`, optional): Period in seconds for which the location will be
updated, should be between
:tg-const:`telegram.InputLocationMessageContent.MIN_LIVE_PERIOD` and
:tg-const:`telegram.InputLocationMessageContent.MAX_LIVE_PERIOD`.
:tg-const:`telegram.InputLocationMessageContent.MAX_LIVE_PERIOD` or
:tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` for live
locations that can be edited indefinitely.
heading (:obj:`int`, optional): For live locations, a direction in which the user is
moving, in degrees. Must be between
:tg-const:`telegram.InputLocationMessageContent.MIN_HEADING` and

View file

@ -25,6 +25,7 @@ from html import escape
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple, TypedDict, Union
from telegram._chat import Chat
from telegram._chatbackground import ChatBackground
from telegram._chatboost import ChatBoostAdded
from telegram._dice import Dice
from telegram._files.animation import Animation
@ -64,6 +65,7 @@ from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.entities import parse_message_entities, parse_message_entity
from telegram._utils.types import (
CorrectOptionID,
FileInput,
@ -99,6 +101,7 @@ if TYPE_CHECKING:
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputPollOption,
LabeledPrice,
MessageId,
MessageOrigin,
@ -553,6 +556,11 @@ class Message(MaybeInaccessibleMessage):
.. versionadded:: 21.1
chat_background_set (:obj:`telegram.ChatBackground`, optional): Service message: chat
background set.
.. versionadded:: NEXT.VERSION
Attributes:
message_id (:obj:`int`): Unique message identifier inside this chat.
from_user (:class:`telegram.User`): Optional. Sender of the message; empty for messages
@ -853,6 +861,11 @@ class Message(MaybeInaccessibleMessage):
.. versionadded:: 21.1
chat_background_set (:obj:`telegram.ChatBackground`): Optional. Service message: chat
background set
.. versionadded:: Next.Version
.. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by
:attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a
:exc:`ValueError` when encountering a custom emoji.
@ -876,6 +889,7 @@ class Message(MaybeInaccessibleMessage):
"caption",
"caption_entities",
"channel_chat_created",
"chat_background_set",
"chat_shared",
"connected_website",
"contact",
@ -1029,6 +1043,7 @@ class Message(MaybeInaccessibleMessage):
business_connection_id: Optional[str] = None,
sender_business_bot: Optional[User] = None,
is_from_offline: Optional[bool] = None,
chat_background_set: Optional[ChatBackground] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -1127,6 +1142,7 @@ class Message(MaybeInaccessibleMessage):
self.business_connection_id: Optional[str] = business_connection_id
self.sender_business_bot: Optional[User] = sender_business_bot
self.is_from_offline: Optional[bool] = is_from_offline
self.chat_background_set: Optional[ChatBackground] = chat_background_set
self._effective_attachment = DEFAULT_NONE
@ -1241,6 +1257,7 @@ class Message(MaybeInaccessibleMessage):
)
data["users_shared"] = UsersShared.de_json(data.get("users_shared"), bot)
data["chat_shared"] = ChatShared.de_json(data.get("chat_shared"), bot)
data["chat_background_set"] = ChatBackground.de_json(data.get("chat_background_set"), bot)
# Unfortunately, this needs to be here due to cyclic imports
from telegram._giveaway import ( # pylint: disable=import-outside-toplevel
@ -2890,7 +2907,7 @@ class Message(MaybeInaccessibleMessage):
async def reply_poll(
self,
question: str,
options: Sequence[str],
options: Sequence[Union[str, "InputPollOption"]],
is_anonymous: Optional[bool] = None,
type: Optional[str] = None, # pylint: disable=redefined-builtin
allows_multiple_answers: Optional[bool] = None,
@ -2906,6 +2923,8 @@ class Message(MaybeInaccessibleMessage):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: ODVInput[int] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2976,6 +2995,8 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
question_parse_mode=question_parse_mode,
question_entities=question_entities,
)
async def reply_dice(
@ -3653,6 +3674,7 @@ class Message(MaybeInaccessibleMessage):
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
live_period: Optional[int] = None,
*,
location: Optional[Location] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -3694,6 +3716,7 @@ class Message(MaybeInaccessibleMessage):
horizontal_accuracy=horizontal_accuracy,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
live_period=live_period,
inline_message_id=None,
)
@ -4184,9 +4207,7 @@ class Message(MaybeInaccessibleMessage):
if not self.text:
raise RuntimeError("This Message has no 'text'.")
entity_text = self.text.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
return parse_message_entity(self.text, entity)
def parse_caption_entity(self, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
@ -4210,9 +4231,7 @@ class Message(MaybeInaccessibleMessage):
if not self.caption:
raise RuntimeError("This Message has no 'caption'.")
entity_text = self.caption.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
return parse_message_entity(self.caption, entity)
def parse_entities(self, types: Optional[List[str]] = None) -> Dict[MessageEntity, str]:
"""
@ -4237,12 +4256,7 @@ class Message(MaybeInaccessibleMessage):
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
return {
entity: self.parse_entity(entity) for entity in self.entities if entity.type in types
}
return parse_message_entities(self.text, self.entities, types=types)
def parse_caption_entities(
self, types: Optional[List[str]] = None
@ -4269,14 +4283,7 @@ class Message(MaybeInaccessibleMessage):
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
return {
entity: self.parse_caption_entity(entity)
for entity in self.caption_entities
if entity.type in types
}
return parse_message_entities(self.caption, self.caption_entities, types=types)
@classmethod
def _parse_html(

View file

@ -28,12 +28,80 @@ from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.entities import parse_message_entities, parse_message_entity
from telegram._utils.types import JSONDict, ODVInput
if TYPE_CHECKING:
from telegram import Bot
class InputPollOption(TelegramObject):
"""
This object contains information about one answer option in a poll to send.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text` is equal.
.. versionadded:: NEXT.VERSION
Args:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
text_parse_mode (:obj:`str`, optional): |parse_mode|
Currently, only custom emoji entities are allowed.
text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities
that appear in the option :paramref:`text`. It can be specified instead of
:paramref:`text_parse_mode`.
Currently, only custom emoji entities are allowed.
This list is empty if the text does not contain entities.
Attributes:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
text_parse_mode (:obj:`str`): Optional. |parse_mode|
Currently, only custom emoji entities are allowed.
text_entities (Sequence[:class:`telegram.MessageEntity`]): Special entities
that appear in the option :paramref:`text`. It can be specified instead of
:paramref:`text_parse_mode`.
Currently, only custom emoji entities are allowed.
This list is empty if the text does not contain entities.
"""
__slots__ = ("text", "text_entities", "text_parse_mode")
def __init__(
self,
text: str,
text_parse_mode: ODVInput[str] = DEFAULT_NONE,
text_entities: Optional[Sequence[MessageEntity]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.text: str = text
self.text_parse_mode: ODVInput[str] = text_parse_mode
self.text_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(text_entities)
self._id_attrs = (self.text,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["InputPollOption"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
return super().de_json(data=data, bot=bot)
class PollOption(TelegramObject):
"""
This object contains information about one answer option in a poll.
@ -46,26 +114,101 @@ class PollOption(TelegramObject):
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
voter_count (:obj:`int`): Number of users that voted for this option.
text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities
that appear in the option text. Currently, only custom emoji entities are allowed in
poll option texts.
.. versionadded:: NEXT.VERSION
Attributes:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
voter_count (:obj:`int`): Number of users that voted for this option.
text_entities (Tuple[:class:`telegram.MessageEntity`]): Special entities
that appear in the option text. Currently, only custom emoji entities are allowed in
poll option texts.
This list is empty if the question does not contain entities.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("text", "voter_count")
__slots__ = ("text", "text_entities", "voter_count")
def __init__(self, text: str, voter_count: int, *, api_kwargs: Optional[JSONDict] = None):
def __init__(
self,
text: str,
voter_count: int,
text_entities: Optional[Sequence[MessageEntity]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.text: str = text
self.voter_count: int = voter_count
self.text_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(text_entities)
self._id_attrs = (self.text, self.voter_count)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["PollOption"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
return super().de_json(data=data, bot=bot)
def parse_entity(self, entity: MessageEntity) -> str:
"""Returns the text in :attr:`text`
from a given :class:`telegram.MessageEntity` of :attr:`text_entities`.
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``Message.text`` with the offset and length.)
.. versionadded:: NEXT.VERSION
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to :attr:`text_entities`.
Returns:
:obj:`str`: The text of the given entity.
"""
return parse_message_entity(self.text, entity)
def parse_entities(self, types: Optional[List[str]] = None) -> Dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this polls question filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
Note:
This method should always be used instead of the :attr:`text_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_entity` for more info.
.. versionadded:: NEXT.VERSION
Args:
types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
return parse_message_entities(self.text, self.text_entities, types)
MIN_LENGTH: Final[int] = constants.PollLimit.MIN_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MIN_OPTION_LENGTH`
@ -215,6 +358,11 @@ class Poll(TelegramObject):
.. versionchanged:: 20.3
|datetime_localization|
question_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities
that appear in the :attr:`question`. Currently, only custom emoji entities are allowed
in poll questions.
.. versionadded:: NEXT.VERSION
Attributes:
id (:obj:`str`): Unique poll identifier.
@ -251,6 +399,12 @@ class Poll(TelegramObject):
.. versionchanged:: 20.3
|datetime_localization|
question_entities (Tuple[:class:`telegram.MessageEntity`]): Special entities
that appear in the :attr:`question`. Currently, only custom emoji entities are allowed
in poll questions.
This list is empty if the question does not contain entities.
.. versionadded:: NEXT.VERSION
"""
@ -266,6 +420,7 @@ class Poll(TelegramObject):
"open_period",
"options",
"question",
"question_entities",
"total_voter_count",
"type",
)
@ -285,6 +440,7 @@ class Poll(TelegramObject):
explanation_entities: Optional[Sequence[MessageEntity]] = None,
open_period: Optional[int] = None,
close_date: Optional[datetime.datetime] = None,
question_entities: Optional[Sequence[MessageEntity]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -304,6 +460,7 @@ class Poll(TelegramObject):
)
self.open_period: Optional[int] = open_period
self.close_date: Optional[datetime.datetime] = close_date
self.question_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(question_entities)
self._id_attrs = (self.id,)
@ -323,11 +480,13 @@ class Poll(TelegramObject):
data["options"] = [PollOption.de_json(option, bot) for option in data["options"]]
data["explanation_entities"] = MessageEntity.de_list(data.get("explanation_entities"), bot)
data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo)
data["question_entities"] = MessageEntity.de_list(data.get("question_entities"), bot)
return super().de_json(data=data, bot=bot)
def parse_explanation_entity(self, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
"""Returns the text in :attr:`explanation` from a given :class:`telegram.MessageEntity` of
:attr:`explanation_entities`.
Note:
This method is present because Telegram calculates the offset and length in
@ -336,7 +495,7 @@ class Poll(TelegramObject):
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to this message.
be an entity that belongs to :attr:`explanation_entities`.
Returns:
:obj:`str`: The text of the given entity.
@ -348,10 +507,7 @@ class Poll(TelegramObject):
if not self.explanation:
raise RuntimeError("This Poll has no 'explanation'.")
entity_text = self.explanation.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
return parse_message_entity(self.explanation, entity)
def parse_explanation_entities(
self, types: Optional[List[str]] = None
@ -375,15 +531,61 @@ class Poll(TelegramObject):
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
Raises:
RuntimeError: If the poll has no explanation.
return {
entity: self.parse_explanation_entity(entity)
for entity in self.explanation_entities
if entity.type in types
}
"""
if not self.explanation:
raise RuntimeError("This Poll has no 'explanation'.")
return parse_message_entities(self.explanation, self.explanation_entities, types)
def parse_question_entity(self, entity: MessageEntity) -> str:
"""Returns the text in :attr:`question` from a given :class:`telegram.MessageEntity` of
:attr:`question_entities`.
.. versionadded:: NEXT.VERSION
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``Message.text`` with the offset and length.)
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to :attr:`question_entities`.
Returns:
:obj:`str`: The text of the given entity.
"""
return parse_message_entity(self.question, entity)
def parse_question_entities(
self, types: Optional[List[str]] = None
) -> Dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this polls question filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
.. versionadded:: NEXT.VERSION
Note:
This method should always be used instead of the :attr:`question_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_question_entity` for more info.
Args:
types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
return parse_message_entities(self.question, self.question_entities, types)
REGULAR: Final[str] = constants.PollType.REGULAR
""":const:`telegram.constants.PollType.REGULAR`"""

View file

@ -355,6 +355,7 @@ class ReplyParameters(TelegramObject):
chat, or in the chat :paramref:`chat_id` if it is specified.
chat_id (:obj:`int` | :obj:`str`, optional): If the message to be replied to is from a
different chat, |chat_id_channel|
Not supported for messages sent on behalf of a business account.
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| Can be
used only for replies in the same chat and forum topic.
quote (:obj:`str`, optional): Quoted part of the message to be replied to; 0-1024
@ -376,6 +377,7 @@ class ReplyParameters(TelegramObject):
chat, or in the chat :paramref:`chat_id` if it is specified.
chat_id (:obj:`int` | :obj:`str`): Optional. If the message to be replied to is from a
different chat, |chat_id_channel|
Not supported for messages sent on behalf of a business account.
allow_sending_without_reply (:obj:`bool`): Optional. |allow_sending_without_reply| Can be
used only for replies in the same chat and forum topic.
quote (:obj:`str`): Optional. Quoted part of the message to be replied to; 0-1024

View file

@ -28,7 +28,8 @@ from telegram._utils.types import JSONDict
class ReplyKeyboardMarkup(TelegramObject):
"""This object represents a custom keyboard with reply options.
"""This object represents a custom keyboard with reply options. Not supported in channels and
for messages sent on behalf of a Telegram Business account.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their size of :attr:`keyboard` and all the buttons are equal.

View file

@ -29,6 +29,7 @@ class ReplyKeyboardRemove(TelegramObject):
keyboard and display the default letter-keyboard. By default, custom keyboards are displayed
until a new keyboard is sent by a bot. An exception is made for one-time keyboards that are
hidden immediately after the user presses a button (see :class:`telegram.ReplyKeyboardMarkup`).
Not supported in channels and for messages sent on behalf of a Telegram Business account.
Note:
User will not be able to summon this keyboard; if you want to hide the keyboard from

View file

@ -40,6 +40,7 @@ if TYPE_CHECKING:
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputPollOption,
LabeledPrice,
LinkPreviewOptions,
Location,
@ -1482,7 +1483,7 @@ class User(TelegramObject):
async def send_poll(
self,
question: str,
options: Sequence[str],
options: Sequence[Union[str, "InputPollOption"]],
is_anonymous: Optional[bool] = None,
type: Optional[str] = None,
allows_multiple_answers: Optional[bool] = None,
@ -1499,6 +1500,8 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1548,6 +1551,8 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
question_parse_mode=question_parse_mode,
question_entities=question_entities,
)
async def send_copy(

View file

@ -0,0 +1,71 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 auxiliary functionality for parsing MessageEntity objects.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from typing import Dict, Optional, Sequence
from telegram._messageentity import MessageEntity
def parse_message_entity(text: str, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
Args:
text (:obj:`str`): The text to extract the entity from.
entity (:class:`telegram.MessageEntity`): The entity to extract the text from.
Returns:
:obj:`str`: The text of the given entity.
"""
entity_text = text.encode("utf-16-le")
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode("utf-16-le")
def parse_message_entities(
text: str, entities: Sequence[MessageEntity], types: Optional[Sequence[str]] = None
) -> Dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
Args:
text (:obj:`str`): The text to extract the entity from.
entities (List[:class:`telegram.MessageEntity`]): The entities to extract the text from.
types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
return {
entity: parse_message_entity(text, entity) for entity in entities if entity.type in types
}

View file

@ -37,6 +37,10 @@ __all__ = [
"SUPPORTED_WEBHOOK_PORTS",
"ZERO_DATE",
"AccentColor",
"BackgroundFillLimit",
"BackgroundFillType",
"BackgroundTypeLimit",
"BackgroundTypeType",
"BotCommandLimit",
"BotCommandScopeType",
"BotDescriptionLimit",
@ -142,7 +146,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=2)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=3)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@ -822,6 +826,46 @@ class ChatLimit(IntEnum):
"""
class BackgroundTypeLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.BackgroundTypeFill`,
:class:`telegram.BackgroundTypeWallpaper` and :class:`telegram.BackgroundTypePattern`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
MAX_DIMMING = 100
""":obj:`int`: Maximum value allowed for:
* :paramref:`~telegram.BackgroundTypeFill.dark_theme_dimming` parameter of
:class:`telegram.BackgroundTypeFill`
* :paramref:`~telegram.BackgroundTypeWallpaper.dark_theme_dimming` parameter of
:class:`telegram.BackgroundTypeWallpaper`
"""
MAX_INTENSITY = 100
""":obj:`int`: Maximum value allowed for :paramref:`~telegram.BackgroundTypePattern.intensity`
parameter of :class:`telegram.BackgroundTypePattern`
"""
class BackgroundFillLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.BackgroundFillGradient`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
MAX_ROTATION_ANGLE = 359
""":obj:`int`: Maximum value allowed for:
:paramref:`~telegram.BackgroundFillGradient.rotation_angle` parameter of
:class:`telegram.BackgroundFillGradient`
"""
class ChatMemberStatus(StringEnum):
"""This enum contains the available states for :class:`telegram.ChatMember`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
@ -1427,6 +1471,21 @@ class LocationLimit(IntEnum):
:meth:`telegram.Bot.send_location`
"""
LIVE_PERIOD_FOREVER = int(hex(0x7FFFFFFF), 16)
""":obj:`int`: Value for live locations that can be edited indefinitely. Passed in:
* :paramref:`~telegram.InlineQueryResultLocation.live_period` parameter of
:class:`telegram.InlineQueryResultLocation`
* :paramref:`~telegram.InputLocationMessageContent.live_period` parameter of
:class:`telegram.InputLocationMessageContent`
* :paramref:`~telegram.Bot.edit_message_live_location.live_period` parameter of
:meth:`telegram.Bot.edit_message_live_location`
* :paramref:`~telegram.Bot.send_location.live_period` parameter of
:meth:`telegram.Bot.send_location`
.. versionadded:: NEXT.VERSION
"""
MIN_PROXIMITY_ALERT_RADIUS = 1
""":obj:`int`: Minimum value allowed for:
@ -1726,6 +1785,11 @@ class MessageType(StringEnum):
.. versionadded:: 20.8
"""
CHAT_BACKGROUND_SET = "chat_background_set"
""":obj:`str`: Messages with :attr:`telegram.Message.chat_background_set`.
.. versionadded:: NEXT.VERSION
"""
CONNECTED_WEBSITE = "connected_website"
""":obj:`str`: Messages with :attr:`telegram.Message.connected_website`."""
CONTACT = "contact"
@ -2878,3 +2942,39 @@ class ReactionEmoji(StringEnum):
""":obj:`str`: Woman Shrugging"""
POUTING_FACE = "😡"
""":obj:`str`: Pouting face"""
class BackgroundTypeType(StringEnum):
"""This enum contains the available types of :class:`telegram.BackgroundType`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
FILL = "fill"
""":obj:`str`: A :class:`telegram.BackgroundType` with fill background."""
WALLPAPER = "wallpaper"
""":obj:`str`: A :class:`telegram.BackgroundType` with wallpaper background."""
PATTERN = "pattern"
""":obj:`str`: A :class:`telegram.BackgroundType` with pattern background."""
CHAT_THEME = "chat_theme"
""":obj:`str`: A :class:`telegram.BackgroundType` with chat_theme background."""
class BackgroundFillType(StringEnum):
"""This enum contains the available types of :class:`telegram.BackgroundFill`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
SOLID = "solid"
""":obj:`str`: A :class:`telegram.BackgroundFill` with solid fill."""
GRADIENT = "gradient"
""":obj:`str`: A :class:`telegram.BackgroundFill` with gradient fill."""
FREEFORM_GRADIENT = "freeform_gradient"
""":obj:`str`: A :class:`telegram.BackgroundFill` with freeform_gradient fill."""

View file

@ -179,13 +179,14 @@ class Defaults:
# Gather all defaults that actually have a default value
self._api_defaults = {}
for kwarg in (
"parse_mode",
"explanation_parse_mode",
"disable_notification",
"allow_sending_without_reply",
"protect_content",
"link_preview_options",
"disable_notification",
"do_quote",
"explanation_parse_mode",
"link_preview_options",
"parse_mode",
"protect_content",
"question_parse_mode",
):
value = getattr(self, kwarg)
if value is not None:
@ -264,6 +265,36 @@ class Defaults:
"You can not assign a new value to quote_parse_mode after initialization."
)
@property
def text_parse_mode(self) -> Optional[str]:
""":obj:`str`: Optional. Alias for :attr:`parse_mode`, used for
the corresponding parameter of :class:`telegram.InputPollOption`.
.. versionadded:: NEXT.VERSION
"""
return self._parse_mode
@text_parse_mode.setter
def text_parse_mode(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to text_parse_mode after initialization."
)
@property
def question_parse_mode(self) -> Optional[str]:
""":obj:`str`: Optional. Alias for :attr:`parse_mode`, used for
the corresponding parameter of :meth:`telegram.Bot.send_poll`.
.. versionadded:: NEXT.VERSION
"""
return self._parse_mode
@question_parse_mode.setter
def question_parse_mode(self, _: object) -> NoReturn:
raise AttributeError(
"You can not assign a new value to question_parse_mode after initialization."
)
@property
def disable_notification(self) -> Optional[bool]:
""":obj:`bool`: Optional. Sends the message silently. Users will

View file

@ -50,8 +50,8 @@ from telegram import (
BotShortDescription,
BusinessConnection,
CallbackQuery,
Chat,
ChatAdministratorRights,
ChatFullInfo,
ChatInviteLink,
ChatMember,
ChatPermissions,
@ -64,6 +64,7 @@ from telegram import (
InlineKeyboardMarkup,
InlineQueryResultsButton,
InputMedia,
InputPollOption,
LinkPreviewOptions,
Location,
MaskPosition,
@ -113,7 +114,7 @@ if TYPE_CHECKING:
)
from telegram.ext import BaseRateLimiter, Defaults
HandledTypes = TypeVar("HandledTypes", bound=Union[Message, CallbackQuery, Chat])
HandledTypes = TypeVar("HandledTypes", bound=Union[Message, CallbackQuery, ChatFullInfo])
KT = TypeVar("KT", bound=ReplyMarkup)
@ -436,6 +437,7 @@ class ExtBot(Bot, Generic[RLARGS]):
# 3) set the correct parse_mode for all InputMedia objects
# 4) handle the LinkPreviewOptions case (see below)
# 5) handle the ReplyParameters case (see below)
# 6) handle text_parse_mode in InputPollOption
for key, val in data.items():
# 1)
if isinstance(val, DefaultValue):
@ -487,6 +489,21 @@ class ExtBot(Bot, Generic[RLARGS]):
data[key] = new_value
# 6)
elif isinstance(val, Sequence) and all(
isinstance(obj, InputPollOption) for obj in val
):
new_val = []
for option in val:
if not isinstance(option.text_parse_mode, DefaultValue):
new_val.append(option)
else:
new_option = copy(option)
with new_option._unfrozen():
new_option.text_parse_mode = self.defaults.text_parse_mode
new_val.append(new_option)
data[key] = new_val
def _replace_keyboard(self, reply_markup: Optional[KT]) -> Optional[KT]:
# If the reply_markup is an inline keyboard and we allow arbitrary callback data, let the
# CallbackDataCache build a new keyboard with the data replaced. Otherwise return the input
@ -554,7 +571,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self.callback_data_cache.process_message(message=obj)
return obj # type: ignore[return-value]
if isinstance(obj, Chat) and obj.pinned_message:
if isinstance(obj, ChatFullInfo) and obj.pinned_message:
self.callback_data_cache.process_message(obj.pinned_message)
return obj
@ -853,7 +870,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
rate_limit_args: Optional[RLARGS] = None,
) -> Chat:
) -> ChatFullInfo:
# We override this method to call self._insert_callback_data
result = await super().get_chat(
chat_id=chat_id,
@ -1520,6 +1537,7 @@ class ExtBot(Bot, Generic[RLARGS]):
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
live_period: Optional[int] = None,
*,
location: Optional[Location] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1539,6 +1557,7 @@ class ExtBot(Bot, Generic[RLARGS]):
horizontal_accuracy=horizontal_accuracy,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
live_period=live_period,
location=location,
read_timeout=read_timeout,
write_timeout=write_timeout,
@ -2915,7 +2934,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
chat_id: Union[int, str],
question: str,
options: Sequence[str],
options: Sequence[Union[str, "InputPollOption"]],
is_anonymous: Optional[bool] = None,
type: Optional[str] = None, # pylint: disable=redefined-builtin
allows_multiple_answers: Optional[bool] = None,
@ -2932,6 +2951,8 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
question_parse_mode: ODVInput[str] = DEFAULT_NONE,
question_entities: Optional[Sequence["MessageEntity"]] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2969,6 +2990,8 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
question_parse_mode=question_parse_mode,
question_entities=question_entities,
)
async def send_sticker(

View file

@ -1909,7 +1909,8 @@ class StatusUpdate:
def filter(self, update: Update) -> bool:
return bool(
# keep this alphabetically sorted for easier maintenance
StatusUpdate.CHAT_CREATED.check_update(update)
StatusUpdate.CHAT_BACKGROUND_SET.check_update(update)
or StatusUpdate.CHAT_CREATED.check_update(update)
or StatusUpdate.CHAT_SHARED.check_update(update)
or StatusUpdate.CONNECTED_WEBSITE.check_update(update)
or StatusUpdate.DELETE_CHAT_PHOTO.check_update(update)
@ -1942,6 +1943,15 @@ class StatusUpdate:
ALL = _All(name="filters.StatusUpdate.ALL")
"""Messages that contain any of the below."""
class _ChatBackgroundSet(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.chat_background_set)
CHAT_BACKGROUND_SET = _ChatBackgroundSet(name="filters.StatusUpdate.CHAT_BACKGROUND_SET")
"""Messages that contain :attr:`telegram.Message.chat_background_set`."""
class _ChatCreated(MessageFilter):
__slots__ = ()

View file

@ -124,7 +124,8 @@ class TestLocationWithoutRequest(TestLocationBase):
ha = data["horizontal_accuracy"] == "50"
heading = data["heading"] == "90"
prox_alert = data["proximity_alert_radius"] == "1000"
return lat and lon and id_ and ha and heading and prox_alert
live = data["live_period"] == "900"
return lat and lon and id_ and ha and heading and prox_alert and live
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.edit_message_live_location(
@ -133,6 +134,7 @@ class TestLocationWithoutRequest(TestLocationBase):
horizontal_accuracy=50,
heading=90,
proximity_alert_radius=1000,
live_period=900,
)
# TODO: Needs improvement with in inline sent live location.
@ -262,6 +264,7 @@ class TestLocationWithRequest:
horizontal_accuracy=30,
heading=10,
proximity_alert_radius=500,
live_period=200,
)
assert pytest.approx(message2.location.latitude, rel=1e-5) == 52.223098
@ -269,6 +272,7 @@ class TestLocationWithRequest:
assert message2.location.horizontal_accuracy == 30
assert message2.location.heading == 10
assert message2.location.proximity_alert_radius == 500
assert message2.location.live_period == 200
await bot.stop_message_live_location(message.chat_id, message.message_id)
with pytest.raises(BadRequest, match="Message can't be edited"):

View file

@ -314,6 +314,8 @@ def build_kwargs(
elif name == "ok":
kws["ok"] = False
kws["error_message"] = "error"
elif name == "options":
kws[name] = ["option1", "option2"]
else:
kws[name] = True

View file

@ -1090,6 +1090,11 @@ class TestFilters:
assert filters.StatusUpdate.GIVEAWAY_COMPLETED.check_update(update)
update.message.giveaway_completed = None
update.message.chat_background_set = "test_background"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.CHAT_BACKGROUND_SET.check_update(update)
update.message.chat_background_set = None
def test_filters_forwarded(self, update, message_origin_user):
assert filters.FORWARDED.check_update(update)
update.message.forward_origin = MessageOriginHiddenUser(datetime.datetime.utcnow(), 1)

View file

@ -43,6 +43,7 @@ from telegram import (
CallbackQuery,
Chat,
ChatAdministratorRights,
ChatFullInfo,
ChatPermissions,
Dice,
InlineKeyboardButton,
@ -55,6 +56,7 @@ from telegram import (
InputMediaDocument,
InputMediaPhoto,
InputMessageContent,
InputPollOption,
InputTextMessageContent,
LabeledPrice,
LinkPreviewOptions,
@ -1937,6 +1939,59 @@ class TestBotWithoutRequest:
chat_id, message, reply_parameters=ReplyParameters(**kwargs)
)
@pytest.mark.parametrize(
("default_bot", "custom"),
[
({"parse_mode": ParseMode.HTML}, "NOTHING"),
({"parse_mode": ParseMode.HTML}, None),
({"parse_mode": ParseMode.HTML}, ParseMode.MARKDOWN_V2),
({"parse_mode": None}, ParseMode.MARKDOWN_V2),
],
indirect=["default_bot"],
)
async def test_send_poll_default_text_question_parse_mode(
self, default_bot, raw_bot, chat_id, custom, monkeypatch
):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
expected = default_bot.defaults.text_parse_mode if custom == "NOTHING" else custom
option_1 = request_data.parameters["options"][0]
option_2 = request_data.parameters["options"][1]
assert option_1.get("text_parse_mode") == (default_bot.defaults.text_parse_mode)
assert option_2.get("text_parse_mode") == expected
assert request_data.parameters.get("question_parse_mode") == expected
return make_message("dummy reply").to_dict()
async def make_raw_assertion(url, request_data: RequestData, *args, **kwargs):
expected = None if custom == "NOTHING" else custom
option_1 = request_data.parameters["options"][0]
option_2 = request_data.parameters["options"][1]
assert option_1.get("text_parse_mode") is None
assert option_2.get("text_parse_mode") == expected
assert request_data.parameters.get("question_parse_mode") == expected
return make_message("dummy reply").to_dict()
if custom == "NOTHING":
option_2 = InputPollOption("option2")
kwargs = {}
else:
option_2 = InputPollOption("option2", text_parse_mode=custom)
kwargs = {"question_parse_mode": custom}
monkeypatch.setattr(default_bot.request, "post", make_assertion)
await default_bot.send_poll(
chat_id, question="question", options=["option1", option_2], **kwargs
)
monkeypatch.setattr(raw_bot.request, "post", make_raw_assertion)
await raw_bot.send_poll(
chat_id, question="question", options=["option1", option_2], **kwargs
)
@pytest.mark.parametrize(
("default_bot", "custom"),
[
@ -1967,6 +2022,30 @@ class TestBotWithoutRequest:
reply_parameters=ReplyParameters(**kwargs),
)
async def test_send_poll_question_parse_mode_entities(self, bot, monkeypatch):
# Currently only custom emoji are supported as entities which we can't test
# We just test that the correct data is passed for now
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
assert request_data.parameters["question_entities"] == [
{"type": "custom_emoji", "offset": 0, "length": 1},
{"type": "custom_emoji", "offset": 2, "length": 1},
]
assert request_data.parameters["question_parse_mode"] == ParseMode.MARKDOWN_V2
return make_message("dummy reply").to_dict()
monkeypatch.setattr(bot.request, "post", make_assertion)
await bot.send_poll(
1,
question="😀😃",
options=["option1", "option2"],
question_entities=[
MessageEntity(MessageEntity.CUSTOM_EMOJI, 0, 1),
MessageEntity(MessageEntity.CUSTOM_EMOJI, 2, 1),
],
question_parse_mode=ParseMode.MARKDOWN_V2,
)
@pytest.mark.parametrize(
("default_bot", "custom"),
[
@ -2326,7 +2405,7 @@ class TestBotWithRequest:
)
async def test_send_and_stop_poll(self, bot, super_group_id, reply_markup):
question = "Is this a test?"
answers = ["Yes", "No", "Maybe"]
answers = ["Yes", InputPollOption("No"), "Maybe"]
explanation = "[Here is a link](https://google.com)"
explanation_entities = [
MessageEntity(MessageEntity.TEXT_LINK, 0, 14, url="https://google.com")
@ -2360,7 +2439,7 @@ class TestBotWithRequest:
assert message.poll
assert message.poll.question == question
assert message.poll.options[0].text == answers[0]
assert message.poll.options[1].text == answers[1]
assert message.poll.options[1].text == answers[1].text
assert message.poll.options[2].text == answers[2]
assert not message.poll.is_anonymous
assert message.poll.allows_multiple_answers
@ -2380,7 +2459,7 @@ class TestBotWithRequest:
assert poll.is_closed
assert poll.options[0].text == answers[0]
assert poll.options[0].voter_count == 0
assert poll.options[1].text == answers[1]
assert poll.options[1].text == answers[1].text
assert poll.options[1].voter_count == 0
assert poll.options[2].text == answers[2]
assert poll.options[2].voter_count == 0
@ -2921,10 +3000,10 @@ class TestBotWithRequest:
await bot.leave_chat(-123456)
async def test_get_chat(self, bot, super_group_id):
chat = await bot.get_chat(super_group_id)
assert chat.type == "supergroup"
assert chat.title == f">>> telegram.Bot(test) @{bot.username}"
assert chat.id == int(super_group_id)
cfi = await bot.get_chat(super_group_id)
assert cfi.type == "supergroup"
assert cfi.title == f">>> telegram.Bot(test) @{bot.username}"
assert cfi.id == int(super_group_id)
async def test_get_chat_administrators(self, bot, channel_id):
admins = await bot.get_chat_administrators(channel_id)
@ -3900,9 +3979,9 @@ class TestBotWithRequest:
)
assert data == "callback_data"
chat = await bot.get_chat(channel_id)
assert chat.pinned_message == message
assert chat.pinned_message.reply_markup == reply_markup
cfi = await bot.get_chat(channel_id)
assert cfi.pinned_message == message
assert cfi.pinned_message.reply_markup == reply_markup
assert await message.unpin() # (not placed in finally block since msg can be unbound)
finally:
bot.callback_data_cache.clear_callback_data()
@ -3915,11 +3994,11 @@ class TestBotWithRequest:
await bot.unpin_all_chat_messages(super_group_id)
try:
chat = await bot.get_chat(super_group_id)
cfi = await bot.get_chat(super_group_id)
assert isinstance(chat, Chat)
assert int(chat.id) == int(super_group_id)
assert chat.pinned_message is None
assert isinstance(cfi, ChatFullInfo)
assert int(cfi.id) == int(super_group_id)
assert cfi.pinned_message is None
finally:
bot.callback_data_cache.clear_callback_data()
bot.callback_data_cache.clear_callback_queries()

View file

@ -301,8 +301,9 @@ class TestCallbackQueryWithoutRequest(TestCallbackQueryBase):
async def make_assertion(*_, **kwargs):
latitude = kwargs.get("latitude") == 1
longitude = kwargs.get("longitude") == 2
live = kwargs.get("live_period") == 900
ids = self.check_passed_ids(callback_query, kwargs)
return ids and latitude and longitude
return ids and latitude and longitude and live
assert check_shortcut_signature(
CallbackQuery.edit_message_live_location,
@ -322,8 +323,10 @@ class TestCallbackQueryWithoutRequest(TestCallbackQueryBase):
)
monkeypatch.setattr(callback_query.get_bot(), "edit_message_live_location", make_assertion)
assert await callback_query.edit_message_live_location(latitude=1, longitude=2)
assert await callback_query.edit_message_live_location(1, 2)
assert await callback_query.edit_message_live_location(
latitude=1, longitude=2, live_period=900
)
assert await callback_query.edit_message_live_location(1, 2, live_period=900)
async def test_stop_message_live_location(self, monkeypatch, callback_query):
if isinstance(callback_query.message, InaccessibleMessage):

View file

@ -16,7 +16,9 @@
#
# 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 datetime
import warnings
import pytest
@ -35,9 +37,11 @@ from telegram import (
ReactionTypeEmoji,
User,
)
from telegram._chat import _deprecated_attrs
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import ChatAction, ChatType, ReactionEmoji
from telegram.helpers import escape_markdown
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.bot_method_checks import (
check_defaults_handling,
check_shortcut_call,
@ -84,6 +88,8 @@ def chat(bot):
business_opening_hours=TestChatBase.business_opening_hours,
birthdate=Birthdate(1, 1),
personal_chat=TestChatBase.personal_chat,
first_name=TestChatBase.first_name,
last_name=TestChatBase.last_name,
)
chat.set_bot(bot)
chat._unfreeze()
@ -137,6 +143,8 @@ class TestChatBase:
custom_emoji_sticker_set_name = "custom_emoji_sticker_set_name"
birthdate = Birthdate(1, 1)
personal_chat = Chat(3, "private", "private")
first_name = "first"
last_name = "last"
class TestChatWithoutRequest(TestChatBase):
@ -185,6 +193,8 @@ class TestChatWithoutRequest(TestChatBase):
"custom_emoji_sticker_set_name": self.custom_emoji_sticker_set_name,
"birthdate": self.birthdate.to_dict(),
"personal_chat": self.personal_chat.to_dict(),
"first_name": self.first_name,
"last_name": self.last_name,
}
chat = Chat.de_json(json_dict, bot)
@ -230,6 +240,8 @@ class TestChatWithoutRequest(TestChatBase):
assert chat.custom_emoji_sticker_set_name == self.custom_emoji_sticker_set_name
assert chat.birthdate == self.birthdate
assert chat.personal_chat == self.personal_chat
assert chat.first_name == self.first_name
assert chat.last_name == self.last_name
def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = {
@ -251,6 +263,15 @@ class TestChatWithoutRequest(TestChatBase):
assert chat_bot_raw.emoji_status_expiration_date.tzinfo == UTC
assert emoji_expire_offset_tz == emoji_expire_offset
def test_always_tuples_attributes(self):
chat = Chat(
id=123,
title="title",
type=Chat.PRIVATE,
)
assert isinstance(chat.active_usernames, tuple)
assert chat.active_usernames == ()
def test_to_dict(self, chat):
chat_dict = chat.to_dict()
@ -300,15 +321,25 @@ class TestChatWithoutRequest(TestChatBase):
assert chat_dict["unrestrict_boost_count"] == chat.unrestrict_boost_count
assert chat_dict["birthdate"] == chat.birthdate.to_dict()
assert chat_dict["personal_chat"] == chat.personal_chat.to_dict()
assert chat_dict["first_name"] == chat.first_name
assert chat_dict["last_name"] == chat.last_name
def test_always_tuples_attributes(self):
chat = Chat(
id=123,
title="title",
type=Chat.PRIVATE,
)
assert isinstance(chat.active_usernames, tuple)
assert chat.active_usernames == ()
def test_deprecated_attributes(self, chat):
for depr_attr in _deprecated_attrs:
with pytest.warns(PTBDeprecationWarning, match="deprecated and will only be accessib"):
getattr(chat, depr_attr)
with warnings.catch_warnings(): # No warning should be raised
warnings.simplefilter("error")
chat.id
chat.first_name
def test_deprecated_arguments(self):
for depr_attr in _deprecated_attrs:
with pytest.warns(PTBDeprecationWarning, match="deprecated and will only be availabl"):
Chat(1, "type", **{depr_attr: "1"})
with warnings.catch_warnings(): # No warning should be raised
warnings.simplefilter("error")
Chat(1, "type", first_name="first_name")
def test_enum_init(self):
chat = Chat(id=1, type="foo")
@ -348,10 +379,7 @@ class TestChatWithoutRequest(TestChatBase):
assert chat.full_name == "first\u2022name last\u2022name"
chat = Chat(id=1, type=Chat.PRIVATE, first_name="first\u2022name")
assert chat.full_name == "first\u2022name"
chat = Chat(
id=1,
type=Chat.PRIVATE,
)
chat = Chat(id=1, type=Chat.PRIVATE)
assert chat.full_name is None
def test_effective_name(self):
@ -588,7 +616,7 @@ class TestChatWithoutRequest(TestChatBase):
async def test_set_permissions(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
chat_id = kwargs["chat_id"] == chat.id
permissions = kwargs["permissions"] == self.permissions
permissions = kwargs["permissions"] == ChatPermissions.all_permissions()
return chat_id and permissions
assert check_shortcut_signature(
@ -600,7 +628,7 @@ class TestChatWithoutRequest(TestChatBase):
assert await check_defaults_handling(chat.set_permissions, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "set_chat_permissions", make_assertion)
assert await chat.set_permissions(permissions=self.permissions)
assert await chat.set_permissions(permissions=ChatPermissions.all_permissions())
async def test_set_administrator_custom_title(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):

View file

@ -0,0 +1,361 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 inspect
from copy import deepcopy
from typing import Union
import pytest
from telegram import (
BackgroundFill,
BackgroundFillFreeformGradient,
BackgroundFillGradient,
BackgroundFillSolid,
BackgroundType,
BackgroundTypeChatTheme,
BackgroundTypeFill,
BackgroundTypePattern,
BackgroundTypeWallpaper,
Dice,
Document,
)
from tests.auxil.slots import mro_slots
ignored = ["self", "api_kwargs"]
class BFDefaults:
color = 0
top_color = 1
bottom_color = 2
rotation_angle = 45
colors = [0, 1, 2]
def background_fill_solid():
return BackgroundFillSolid(BFDefaults.color)
def background_fill_gradient():
return BackgroundFillGradient(
BFDefaults.top_color, BFDefaults.bottom_color, BFDefaults.rotation_angle
)
def background_fill_freeform_gradient():
return BackgroundFillFreeformGradient(BFDefaults.colors)
class BTDefaults:
document = Document(1, 2)
fill = BackgroundFillSolid(color=0)
dark_theme_dimming = 20
is_blurred = True
is_moving = False
intensity = 90
is_inverted = False
theme_name = "ice"
def background_type_fill():
return BackgroundTypeFill(BTDefaults.fill, BTDefaults.dark_theme_dimming)
def background_type_wallpaper():
return BackgroundTypeWallpaper(
BTDefaults.document,
BTDefaults.dark_theme_dimming,
BTDefaults.is_blurred,
BTDefaults.is_moving,
)
def background_type_pattern():
return BackgroundTypePattern(
BTDefaults.document,
BTDefaults.fill,
BTDefaults.intensity,
BTDefaults.is_inverted,
BTDefaults.is_moving,
)
def background_type_chat_theme():
return BackgroundTypeChatTheme(BTDefaults.theme_name)
def make_json_dict(
instance: Union[BackgroundType, BackgroundFill], include_optional_args: bool = False
) -> dict:
"""Used to make the json dict which we use for testing de_json. Similar to iter_args()"""
json_dict = {"type": instance.type}
sig = inspect.signature(instance.__class__.__init__)
for param in sig.parameters.values():
if param.name in ignored: # ignore irrelevant params
continue
val = getattr(instance, param.name)
# Compulsory args-
if param.default is inspect.Parameter.empty:
if hasattr(val, "to_dict"): # convert the user object or any future ones to dict.
val = val.to_dict()
json_dict[param.name] = val
# If we want to test all args (for de_json)-
elif param.default is not inspect.Parameter.empty and include_optional_args:
json_dict[param.name] = val
return json_dict
def iter_args(
instance: Union[BackgroundType, BackgroundFill],
de_json_inst: Union[BackgroundType, BackgroundFill],
include_optional: bool = False,
):
"""
We accept both the regular instance and de_json created instance and iterate over them for
easy one line testing later one.
"""
yield instance.type, de_json_inst.type # yield this here cause it's not available in sig.
sig = inspect.signature(instance.__class__.__init__)
for param in sig.parameters.values():
if param.name in ignored:
continue
inst_at, json_at = getattr(instance, param.name), getattr(de_json_inst, param.name)
if (
param.default is not inspect.Parameter.empty and include_optional
) or param.default is inspect.Parameter.empty:
yield inst_at, json_at
@pytest.fixture()
def background_type(request):
return request.param()
@pytest.mark.parametrize(
"background_type",
[
background_type_fill,
background_type_wallpaper,
background_type_pattern,
background_type_chat_theme,
],
indirect=True,
)
class TestBackgroundTypeWithoutRequest:
def test_slot_behaviour(self, background_type):
inst = background_type
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_de_json_required_args(self, bot, background_type):
cls = background_type.__class__
assert cls.de_json({}, bot) is None
json_dict = make_json_dict(background_type)
const_background_type = BackgroundType.de_json(json_dict, bot)
assert const_background_type.api_kwargs == {}
assert isinstance(const_background_type, BackgroundType)
assert isinstance(const_background_type, cls)
for bg_type_at, const_bg_type_at in iter_args(background_type, const_background_type):
assert bg_type_at == const_bg_type_at
def test_de_json_all_args(self, bot, background_type):
json_dict = make_json_dict(background_type, include_optional_args=True)
const_background_type = BackgroundType.de_json(json_dict, bot)
assert const_background_type.api_kwargs == {}
assert isinstance(const_background_type, BackgroundType)
assert isinstance(const_background_type, background_type.__class__)
for bg_type_at, const_bg_type_at in iter_args(
background_type, const_background_type, True
):
assert bg_type_at == const_bg_type_at
def test_de_json_invalid_type(self, background_type, bot):
json_dict = {"type": "invalid", "theme_name": BTDefaults.theme_name}
background_type = BackgroundType.de_json(json_dict, bot)
assert type(background_type) is BackgroundType
assert background_type.type == "invalid"
def test_de_json_subclass(self, background_type, bot, chat_id):
"""This makes sure that e.g. BackgroundTypeFill(data, bot) never returns a
BackgroundTypeWallpaper instance."""
cls = background_type.__class__
json_dict = make_json_dict(background_type, True)
assert type(cls.de_json(json_dict, bot)) is cls
def test_to_dict(self, background_type):
bg_type_dict = background_type.to_dict()
assert isinstance(bg_type_dict, dict)
assert bg_type_dict["type"] == background_type.type
for slot in background_type.__slots__: # additional verification for the optional args
if slot in ("fill", "document"):
assert (getattr(background_type, slot)).to_dict() == bg_type_dict[slot]
continue
assert getattr(background_type, slot) == bg_type_dict[slot]
def test_equality(self, background_type):
a = BackgroundType(type="type")
b = BackgroundType(type="type")
c = background_type
d = deepcopy(background_type)
e = Dice(4, "emoji")
sig = inspect.signature(background_type.__class__.__init__)
params = [
"random" for param in sig.parameters.values() if param.name not in [*ignored, "type"]
]
f = background_type.__class__(*params)
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)
assert c == d
assert hash(c) == hash(d)
assert c != e
assert hash(c) != hash(e)
assert f != c
assert hash(f) != hash(c)
@pytest.fixture()
def background_fill(request):
return request.param()
@pytest.mark.parametrize(
"background_fill",
[
background_fill_solid,
background_fill_gradient,
background_fill_freeform_gradient,
],
indirect=True,
)
class TestBackgroundFillWithoutRequest:
def test_slot_behaviour(self, background_fill):
inst = background_fill
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_de_json_required_args(self, bot, background_fill):
cls = background_fill.__class__
assert cls.de_json({}, bot) is None
json_dict = make_json_dict(background_fill)
const_background_fill = BackgroundFill.de_json(json_dict, bot)
assert const_background_fill.api_kwargs == {}
assert isinstance(const_background_fill, BackgroundFill)
assert isinstance(const_background_fill, cls)
for bg_fill_at, const_bg_fill_at in iter_args(background_fill, const_background_fill):
assert bg_fill_at == const_bg_fill_at
def test_de_json_all_args(self, bot, background_fill):
json_dict = make_json_dict(background_fill, include_optional_args=True)
const_background_fill = BackgroundFill.de_json(json_dict, bot)
assert const_background_fill.api_kwargs == {}
assert isinstance(const_background_fill, BackgroundFill)
assert isinstance(const_background_fill, background_fill.__class__)
for bg_fill_at, const_bg_fill_at in iter_args(
background_fill, const_background_fill, True
):
assert bg_fill_at == const_bg_fill_at
def test_de_json_invalid_type(self, background_fill, bot):
json_dict = {"type": "invalid", "theme_name": BTDefaults.theme_name}
background_fill = BackgroundFill.de_json(json_dict, bot)
assert type(background_fill) is BackgroundFill
assert background_fill.type == "invalid"
def test_de_json_subclass(self, background_fill, bot):
"""This makes sure that e.g. BackgroundFillSolid(data, bot) never returns a
BackgroundFillGradient instance."""
cls = background_fill.__class__
json_dict = make_json_dict(background_fill, True)
assert type(cls.de_json(json_dict, bot)) is cls
def test_to_dict(self, background_fill):
bg_fill_dict = background_fill.to_dict()
assert isinstance(bg_fill_dict, dict)
assert bg_fill_dict["type"] == background_fill.type
for slot in background_fill.__slots__: # additional verification for the optional args
if slot == "colors":
assert getattr(background_fill, slot) == tuple(bg_fill_dict[slot])
continue
assert getattr(background_fill, slot) == bg_fill_dict[slot]
def test_equality(self, background_fill):
a = BackgroundFill(type="type")
b = BackgroundFill(type="type")
c = background_fill
d = deepcopy(background_fill)
e = Dice(4, "emoji")
sig = inspect.signature(background_fill.__class__.__init__)
params = [
"random" for param in sig.parameters.values() if param.name not in [*ignored, "type"]
]
f = background_fill.__class__(*params)
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)
assert c == d
assert hash(c) == hash(d)
assert c != e
assert hash(c) != hash(e)
assert f != c
assert hash(f) != hash(c)

209
tests/test_chatfullinfo.py Normal file
View file

@ -0,0 +1,209 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 datetime
import warnings
import pytest
from telegram import (
Birthdate,
BusinessIntro,
BusinessLocation,
BusinessOpeningHours,
BusinessOpeningHoursInterval,
Chat,
ChatFullInfo,
ChatLocation,
ChatPermissions,
Location,
ReactionTypeCustomEmoji,
ReactionTypeEmoji,
)
from telegram._chat import _deprecated_attrs
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import ReactionEmoji
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def chat_full_info(bot):
chat = ChatFullInfo(
TestChatInfoBase.id_,
type=TestChatInfoBase.type_,
accent_color_id=TestChatInfoBase.accent_color_id,
max_reaction_count=TestChatInfoBase.max_reaction_count,
title=TestChatInfoBase.title,
username=TestChatInfoBase.username,
sticker_set_name=TestChatInfoBase.sticker_set_name,
can_set_sticker_set=TestChatInfoBase.can_set_sticker_set,
permissions=TestChatInfoBase.permissions,
slow_mode_delay=TestChatInfoBase.slow_mode_delay,
bio=TestChatInfoBase.bio,
linked_chat_id=TestChatInfoBase.linked_chat_id,
location=TestChatInfoBase.location,
has_private_forwards=True,
has_protected_content=True,
has_visible_history=True,
join_to_send_messages=True,
join_by_request=True,
has_restricted_voice_and_video_messages=True,
is_forum=True,
active_usernames=TestChatInfoBase.active_usernames,
emoji_status_custom_emoji_id=TestChatInfoBase.emoji_status_custom_emoji_id,
emoji_status_expiration_date=TestChatInfoBase.emoji_status_expiration_date,
has_aggressive_anti_spam_enabled=TestChatInfoBase.has_aggressive_anti_spam_enabled,
has_hidden_members=TestChatInfoBase.has_hidden_members,
available_reactions=TestChatInfoBase.available_reactions,
background_custom_emoji_id=TestChatInfoBase.background_custom_emoji_id,
profile_accent_color_id=TestChatInfoBase.profile_accent_color_id,
profile_background_custom_emoji_id=TestChatInfoBase.profile_background_custom_emoji_id,
unrestrict_boost_count=TestChatInfoBase.unrestrict_boost_count,
custom_emoji_sticker_set_name=TestChatInfoBase.custom_emoji_sticker_set_name,
business_intro=TestChatInfoBase.business_intro,
business_location=TestChatInfoBase.business_location,
business_opening_hours=TestChatInfoBase.business_opening_hours,
birthdate=Birthdate(1, 1),
personal_chat=TestChatInfoBase.personal_chat,
)
chat.set_bot(bot)
chat._unfreeze()
return chat
class TestChatInfoBase:
id_ = -28767330
max_reaction_count = 2
title = "ToledosPalaceBot - Group"
type_ = "group"
username = "username"
all_members_are_administrators = False
sticker_set_name = "stickers"
can_set_sticker_set = False
permissions = ChatPermissions(
can_send_messages=True,
can_change_info=False,
can_invite_users=True,
)
slow_mode_delay = 30
bio = "I'm a Barbie Girl in a Barbie World"
linked_chat_id = 11880
location = ChatLocation(Location(123, 456), "Barbie World")
has_protected_content = True
has_visible_history = True
has_private_forwards = True
join_to_send_messages = True
join_by_request = True
has_restricted_voice_and_video_messages = True
is_forum = True
active_usernames = ["These", "Are", "Usernames!"]
emoji_status_custom_emoji_id = "VeryUniqueCustomEmojiID"
emoji_status_expiration_date = datetime.datetime.now(tz=UTC).replace(microsecond=0)
has_aggressive_anti_spam_enabled = True
has_hidden_members = True
available_reactions = [
ReactionTypeEmoji(ReactionEmoji.THUMBS_DOWN),
ReactionTypeCustomEmoji("custom_emoji_id"),
]
business_intro = BusinessIntro("Title", "Description", None)
business_location = BusinessLocation("Address", Location(123, 456))
business_opening_hours = BusinessOpeningHours(
"Country/City",
[BusinessOpeningHoursInterval(opening, opening + 60) for opening in (0, 24 * 60)],
)
accent_color_id = 1
background_custom_emoji_id = "background_custom_emoji_id"
profile_accent_color_id = 2
profile_background_custom_emoji_id = "profile_background_custom_emoji_id"
unrestrict_boost_count = 100
custom_emoji_sticker_set_name = "custom_emoji_sticker_set_name"
birthdate = Birthdate(1, 1)
personal_chat = Chat(3, "private", "private")
class TestChatWithoutRequest(TestChatInfoBase):
def test_slot_behaviour(self, chat_full_info):
cfi = chat_full_info
for attr in cfi.__slots__:
assert getattr(cfi, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(cfi)) == len(set(mro_slots(cfi))), "duplicate slot"
def test_de_json(self, bot):
json_dict = {
"id": self.id_,
"title": self.title,
"type": self.type_,
"accent_color_id": self.accent_color_id,
"max_reaction_count": self.max_reaction_count,
"username": self.username,
"all_members_are_administrators": self.all_members_are_administrators,
"sticker_set_name": self.sticker_set_name,
"can_set_sticker_set": self.can_set_sticker_set,
"permissions": self.permissions.to_dict(),
"slow_mode_delay": self.slow_mode_delay,
"bio": self.bio,
"business_intro": self.business_intro.to_dict(),
"business_location": self.business_location.to_dict(),
"business_opening_hours": self.business_opening_hours.to_dict(),
"has_protected_content": self.has_protected_content,
"has_visible_history": self.has_visible_history,
"has_private_forwards": self.has_private_forwards,
"linked_chat_id": self.linked_chat_id,
"location": self.location.to_dict(),
"join_to_send_messages": self.join_to_send_messages,
"join_by_request": self.join_by_request,
"has_restricted_voice_and_video_messages": (
self.has_restricted_voice_and_video_messages
),
"is_forum": self.is_forum,
"active_usernames": self.active_usernames,
"emoji_status_custom_emoji_id": self.emoji_status_custom_emoji_id,
"emoji_status_expiration_date": to_timestamp(self.emoji_status_expiration_date),
"has_aggressive_anti_spam_enabled": self.has_aggressive_anti_spam_enabled,
"has_hidden_members": self.has_hidden_members,
"available_reactions": [reaction.to_dict() for reaction in self.available_reactions],
"background_custom_emoji_id": self.background_custom_emoji_id,
"profile_accent_color_id": self.profile_accent_color_id,
"profile_background_custom_emoji_id": self.profile_background_custom_emoji_id,
"unrestrict_boost_count": self.unrestrict_boost_count,
"custom_emoji_sticker_set_name": self.custom_emoji_sticker_set_name,
"birthdate": self.birthdate.to_dict(),
"personal_chat": self.personal_chat.to_dict(),
}
cfi = ChatFullInfo.de_json(json_dict, bot)
assert cfi.max_reaction_count == self.max_reaction_count
def test_to_dict(self, chat_full_info):
cfi = chat_full_info
cfi_dict = cfi.to_dict()
assert isinstance(cfi_dict, dict)
assert cfi_dict["max_reaction_count"] == cfi.max_reaction_count
def test_attr_access_no_warning(self, chat_full_info):
cfi = chat_full_info
for depr_attr in _deprecated_attrs:
with warnings.catch_warnings(): # No warning should be raised
warnings.simplefilter("error")
getattr(cfi, depr_attr)
def test_cfi_creation_no_warning(self, chat_full_info):
cfi = chat_full_info
with warnings.catch_warnings():
dict = cfi.to_dict()
ChatFullInfo(**dict)

View file

@ -82,7 +82,9 @@ def invite_link(user):
@pytest.fixture(scope="module")
def chat_member_updated(user, chat, old_chat_member, new_chat_member, invite_link, time):
return ChatMemberUpdated(chat, user, time, old_chat_member, new_chat_member, invite_link, True)
return ChatMemberUpdated(
chat, user, time, old_chat_member, new_chat_member, invite_link, True, True
)
class TestChatMemberUpdatedBase:
@ -129,6 +131,7 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
"new_chat_member": new_chat_member.to_dict(),
"invite_link": invite_link.to_dict(),
"via_chat_folder_invite_link": True,
"via_join_request": True,
}
chat_member_updated = ChatMemberUpdated.de_json(json_dict, bot)
@ -142,6 +145,7 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
assert chat_member_updated.new_chat_member == new_chat_member
assert chat_member_updated.invite_link == invite_link
assert chat_member_updated.via_chat_folder_invite_link is True
assert chat_member_updated.via_join_request is True
def test_de_json_localization(
self, bot, raw_bot, tz_bot, user, chat, old_chat_member, new_chat_member, time, invite_link
@ -188,6 +192,7 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
chat_member_updated_dict["via_chat_folder_invite_link"]
== chat_member_updated.via_chat_folder_invite_link
)
assert chat_member_updated_dict["via_join_request"] == chat_member_updated.via_join_request
def test_equality(self, time, old_chat_member, new_chat_member, invite_link):
a = ChatMemberUpdated(

View file

@ -27,7 +27,10 @@ exclude_dirs = {
/ "_passport",
}
exclude_patterns = {re.compile(re.escape("self.type: ReactionType = type"))}
exclude_patterns = {
re.compile(re.escape("self.type: ReactionType = type")),
re.compile(re.escape("self.type: BackgroundType = type")),
}
def test_types_are_converted_to_enum():

View file

@ -24,8 +24,10 @@ import pytest
from telegram import (
Animation,
Audio,
BackgroundTypeChatTheme,
Bot,
Chat,
ChatBackground,
ChatBoostAdded,
ChatShared,
Contact,
@ -270,6 +272,7 @@ def message(bot):
{"is_from_offline": True},
{"sender_business_bot": User(1, "BusinessBot", True)},
{"business_connection_id": "123456789"},
{"chat_background_set": ChatBackground(type=BackgroundTypeChatTheme("ice"))},
],
ids=[
"reply",
@ -338,6 +341,7 @@ def message(bot):
"sender_business_bot",
"business_connection_id",
"is_from_offline",
"chat_background_set",
],
)
def message_params(bot, request):
@ -2414,7 +2418,8 @@ class TestMessageWithoutRequest(TestMessageBase):
message_id = kwargs["message_id"] == message.message_id
latitude = kwargs["latitude"] == 1
longitude = kwargs["longitude"] == 2
return chat_id and message_id and longitude and latitude
live = kwargs["live_period"] == 900
return chat_id and message_id and longitude and latitude and live
assert check_shortcut_signature(
Message.edit_live_location,
@ -2432,7 +2437,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert await check_defaults_handling(message.edit_live_location, message.get_bot())
monkeypatch.setattr(message.get_bot(), "edit_message_live_location", make_assertion)
assert await message.edit_live_location(latitude=1, longitude=2)
assert await message.edit_live_location(latitude=1, longitude=2, live_period=900)
async def test_stop_live_location(self, monkeypatch, message):
async def make_assertion(*_, **kwargs):

View file

@ -148,8 +148,11 @@ def check_param_type(
# Now let's do the checking, starting with "Array of ..." types.
if "Array of " in tg_param_type:
# For exceptions just check if they contain the annotation
if ptb_param.name in PTCE.ARRAY_OF_EXCEPTIONS:
return PTCE.ARRAY_OF_EXCEPTIONS[ptb_param.name] in str(ptb_annotation), Sequence
if any(ptb_param.name in key for key in PTCE.ARRAY_OF_EXCEPTIONS):
for (p_name, is_expected_class), exception_type in PTCE.ARRAY_OF_EXCEPTIONS.items():
if ptb_param.name == p_name and is_class is is_expected_class:
log("Checking that `%s` is an exception!\n", ptb_param.name)
return exception_type in str(ptb_annotation), Sequence
obj_match: re.Match | None = re.search(ARRAY_OF_PATTERN, tg_param_type)
if obj_match is None:

View file

@ -20,6 +20,7 @@
from telegram import Animation, Audio, Document, PhotoSize, Sticker, Video, VideoNote, Voice
from telegram._chat import _deprecated_attrs
from tests.test_official.helpers import _get_params_base
IGNORED_OBJECTS = ("ResponseParameters",)
@ -47,15 +48,17 @@ class ParamTypeCheckingExceptions:
"sticker": Sticker,
}
# TODO: Look into merging this with COMPLEX_TYPES
# Exceptions to the "Array of" types, where we accept more types than the official API
# key: parameter name, value: type which must be present in the annotation
# key: (parameter name, is_class), value: type which must be present in the annotation
ARRAY_OF_EXCEPTIONS = {
"results": "InlineQueryResult", # + Callable
"commands": "BotCommand", # + tuple[str, str]
"keyboard": "KeyboardButton", # + sequence[sequence[str]]
"reaction": "ReactionType", # + str
("results", False): "InlineQueryResult", # + Callable
("commands", False): "BotCommand", # + tuple[str, str]
("keyboard", True): "KeyboardButton", # + sequence[sequence[str]]
("reaction", False): "ReactionType", # + str
("options", False): "InputPollOption", # + str
# TODO: Deprecated and will be corrected (and removed) in next major PTB version:
"file_hashes": "List[str]",
("file_hashes", True): "List[str]",
}
# Special cases for other parameters that accept more types than the official API, and are
@ -120,6 +123,8 @@ PTB_EXTRA_PARAMS = {
"ChatBoostSource": {"source"}, # attributes common to all subclasses
"MessageOrigin": {"type", "date"}, # attributes common to all subclasses
"ReactionType": {"type"}, # attributes common to all subclasses
"BackgroundType": {"type"}, # attributes common to all subclasses
"BackgroundFill": {"type"}, # attributes common to all subclasses
"InputTextMessageContent": {"disable_web_page_preview"}, # convenience arg, here for bw compat
}
@ -143,6 +148,8 @@ PTB_IGNORED_PARAMS = {
r"MessageOrigin\w+": {"type"},
r"ChatBoostSource\w+": {"source"},
r"ReactionType\w+": {"type"},
r"BackgroundType\w+": {"type"},
r"BackgroundFill\w+": {"type"},
}
@ -166,7 +173,9 @@ 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]] = {
"Chat": set(_deprecated_attrs), # removed by bot api 7.3
}
def backwards_compat_kwargs(object_name: str) -> set[str]:

View file

@ -19,15 +19,97 @@ from datetime import datetime, timedelta, timezone
import pytest
from telegram import Chat, MessageEntity, Poll, PollAnswer, PollOption, User
from telegram import Chat, InputPollOption, MessageEntity, Poll, PollAnswer, PollOption, User
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import PollType
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def input_poll_option():
out = InputPollOption(
text=TestInputPollOptionBase.text,
text_parse_mode=TestInputPollOptionBase.text_parse_mode,
text_entities=TestInputPollOptionBase.text_entities,
)
out._unfreeze()
return out
class TestInputPollOptionBase:
text = "test option"
text_parse_mode = "MarkdownV2"
text_entities = [
MessageEntity(0, 4, MessageEntity.BOLD),
MessageEntity(5, 7, MessageEntity.ITALIC),
]
class TestInputPollOptionWithoutRequest(TestInputPollOptionBase):
def test_slot_behaviour(self, input_poll_option):
for attr in input_poll_option.__slots__:
assert getattr(input_poll_option, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(input_poll_option)) == len(
set(mro_slots(input_poll_option))
), "duplicate slot"
def test_de_json(self):
assert InputPollOption.de_json({}, None) is None
json_dict = {
"text": self.text,
"text_parse_mode": self.text_parse_mode,
"text_entities": [e.to_dict() for e in self.text_entities],
}
input_poll_option = InputPollOption.de_json(json_dict, None)
assert input_poll_option.api_kwargs == {}
assert input_poll_option.text == self.text
assert input_poll_option.text_parse_mode == self.text_parse_mode
assert input_poll_option.text_entities == tuple(self.text_entities)
def test_to_dict(self, input_poll_option):
input_poll_option_dict = input_poll_option.to_dict()
assert isinstance(input_poll_option_dict, dict)
assert input_poll_option_dict["text"] == input_poll_option.text
assert input_poll_option_dict["text_parse_mode"] == input_poll_option.text_parse_mode
assert input_poll_option_dict["text_entities"] == [
e.to_dict() for e in input_poll_option.text_entities
]
# Test that the default-value parameter is handled correctly
input_poll_option = InputPollOption("text")
input_poll_option_dict = input_poll_option.to_dict()
assert "text_parse_mode" not in input_poll_option_dict
def test_equality(self):
a = InputPollOption("text")
b = InputPollOption("text", self.text_parse_mode)
c = InputPollOption("text", text_entities=self.text_entities)
d = InputPollOption("different_text")
e = Poll(123, "question", ["O1", "O2"], 1, False, True, Poll.REGULAR, True)
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)
@pytest.fixture(scope="module")
def poll_option():
out = PollOption(text=TestPollOptionBase.text, voter_count=TestPollOptionBase.voter_count)
out = PollOption(
text=TestPollOptionBase.text,
voter_count=TestPollOptionBase.voter_count,
text_entities=TestPollOptionBase.text_entities,
)
out._unfreeze()
return out
@ -35,6 +117,10 @@ def poll_option():
class TestPollOptionBase:
text = "test option"
voter_count = 3
text_entities = [
MessageEntity(MessageEntity.BOLD, 0, 4),
MessageEntity(MessageEntity.ITALIC, 5, 6),
]
class TestPollOptionWithoutRequest(TestPollOptionBase):
@ -51,12 +137,43 @@ class TestPollOptionWithoutRequest(TestPollOptionBase):
assert poll_option.text == self.text
assert poll_option.voter_count == self.voter_count
def test_de_json_all(self):
json_dict = {
"text": self.text,
"voter_count": self.voter_count,
"text_entities": [e.to_dict() for e in self.text_entities],
}
poll_option = PollOption.de_json(json_dict, None)
assert PollOption.de_json(None, None) is None
assert poll_option.api_kwargs == {}
assert poll_option.text == self.text
assert poll_option.voter_count == self.voter_count
assert poll_option.text_entities == tuple(self.text_entities)
def test_to_dict(self, poll_option):
poll_option_dict = poll_option.to_dict()
assert isinstance(poll_option_dict, dict)
assert poll_option_dict["text"] == poll_option.text
assert poll_option_dict["voter_count"] == poll_option.voter_count
assert poll_option_dict["text_entities"] == [
e.to_dict() for e in poll_option.text_entities
]
def test_parse_entity(self, poll_option):
entity = MessageEntity(MessageEntity.BOLD, 0, 4)
poll_option.text_entities = [entity]
assert poll_option.parse_entity(entity) == "test"
def test_parse_entities(self, poll_option):
entity = MessageEntity(MessageEntity.BOLD, 0, 4)
entity_2 = MessageEntity(MessageEntity.ITALIC, 5, 6)
poll_option.text_entities = [entity, entity_2]
assert poll_option.parse_entities(MessageEntity.BOLD) == {entity: "test"}
assert poll_option.parse_entities() == {entity: "test", entity_2: "option"}
def test_equality(self):
a = PollOption("text", 1)
@ -159,6 +276,7 @@ def poll():
explanation_entities=TestPollBase.explanation_entities,
open_period=TestPollBase.open_period,
close_date=TestPollBase.close_date,
question_entities=TestPollBase.question_entities,
)
poll._unfreeze()
return poll
@ -166,7 +284,7 @@ def poll():
class TestPollBase:
id_ = "id"
question = "Test?"
question = "Test Question?"
options = [PollOption("test", 10), PollOption("test2", 11)]
total_voter_count = 0
is_closed = True
@ -180,6 +298,10 @@ class TestPollBase:
explanation_entities = [MessageEntity(13, 17, MessageEntity.URL)]
open_period = 42
close_date = datetime.now(timezone.utc)
question_entities = [
MessageEntity(MessageEntity.BOLD, 0, 4),
MessageEntity(MessageEntity.ITALIC, 5, 8),
]
class TestPollWithoutRequest(TestPollBase):
@ -197,6 +319,7 @@ class TestPollWithoutRequest(TestPollBase):
"explanation_entities": [self.explanation_entities[0].to_dict()],
"open_period": self.open_period,
"close_date": to_timestamp(self.close_date),
"question_entities": [e.to_dict() for e in self.question_entities],
}
poll = Poll.de_json(json_dict, bot)
assert poll.api_kwargs == {}
@ -218,6 +341,7 @@ class TestPollWithoutRequest(TestPollBase):
assert poll.open_period == self.open_period
assert abs(poll.close_date - self.close_date) < timedelta(seconds=1)
assert to_timestamp(poll.close_date) == to_timestamp(self.close_date)
assert poll.question_entities == tuple(self.question_entities)
def test_de_json_localization(self, tz_bot, bot, raw_bot):
json_dict = {
@ -233,6 +357,7 @@ class TestPollWithoutRequest(TestPollBase):
"explanation_entities": [self.explanation_entities[0].to_dict()],
"open_period": self.open_period,
"close_date": to_timestamp(self.close_date),
"question_entities": [e.to_dict() for e in self.question_entities],
}
poll_raw = Poll.de_json(json_dict, raw_bot)
@ -265,6 +390,7 @@ class TestPollWithoutRequest(TestPollBase):
assert poll_dict["explanation_entities"] == [poll.explanation_entities[0].to_dict()]
assert poll_dict["open_period"] == poll.open_period
assert poll_dict["close_date"] == to_timestamp(poll.close_date)
assert poll_dict["question_entities"] == [e.to_dict() for e in poll.question_entities]
def test_equality(self):
a = Poll(123, "question", ["O1", "O2"], 1, False, True, Poll.REGULAR, True)
@ -305,7 +431,7 @@ class TestPollWithoutRequest(TestPollBase):
)
assert poll.type is PollType.QUIZ
def test_parse_entity(self, poll):
def test_parse_explanation_entity(self, poll):
entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17)
poll.explanation_entities = [entity]
@ -323,10 +449,36 @@ class TestPollWithoutRequest(TestPollBase):
allows_multiple_answers=False,
).parse_explanation_entity(entity)
def test_parse_entities(self, poll):
def test_parse_explanation_entities(self, poll):
entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17)
entity_2 = MessageEntity(type=MessageEntity.BOLD, offset=13, length=1)
poll.explanation_entities = [entity_2, entity]
assert poll.parse_explanation_entities(MessageEntity.URL) == {entity: "http://google.com"}
assert poll.parse_explanation_entities() == {entity: "http://google.com", entity_2: "h"}
with pytest.raises(RuntimeError, match="Poll has no"):
Poll(
"id",
"question",
[PollOption("text", voter_count=0)],
total_voter_count=0,
is_closed=False,
is_anonymous=False,
type=Poll.QUIZ,
allows_multiple_answers=False,
).parse_explanation_entities()
def test_parse_question_entity(self, poll):
entity = MessageEntity(MessageEntity.ITALIC, 5, 8)
poll.question_entities = [entity]
assert poll.parse_question_entity(entity) == "Question"
def test_parse_question_entities(self, poll):
entity = MessageEntity(MessageEntity.ITALIC, 5, 8)
entity_2 = MessageEntity(MessageEntity.BOLD, 0, 4)
poll.question_entities = [entity_2, entity]
assert poll.parse_question_entities(MessageEntity.ITALIC) == {entity: "Question"}
assert poll.parse_question_entities() == {entity: "Question", entity_2: "Test"}