Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
This commit is contained in:
Poolitzer 2024-03-02 10:56:15 +01:00 committed by GitHub
parent 26f943771b
commit 099ab5d9fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 580 additions and 98 deletions

View file

@ -1,13 +1,12 @@
name: Test Documentation Build
on:
pull_request:
branches:
- master
- doc-fixes
paths:
- telegram/**
- docs/**
push:
branches:
- master
- doc-fixes
jobs:
test-sphinx-build:

View file

@ -1,8 +1,9 @@
name: Bot API Tests
on:
pull_request:
branches:
- master
paths:
- telegram/**
- tests/**
push:
branches:
- master

View file

@ -1,8 +1,8 @@
name: Check Type Completeness
on:
pull_request:
branches:
- master
paths:
- telegram/**
push:
branches:
- master

View file

@ -1,13 +1,12 @@
name: Unit Tests
on:
pull_request:
branches:
- master
paths:
- telegram/**
- tests/**
push:
branches:
- master
schedule:
# Run monday and friday morning at 03:07 - odd time to spread load on GitHub Actions
- cron: '7 3 * * 1,5'

View file

@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-7.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@ -93,7 +93,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.0** are supported.
All types and methods of the Telegram Bot API **7.1** are supported.
Installing
==========

View file

@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-7.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@ -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.0** are supported.
All types and methods of the Telegram Bot API **7.1** are supported.
Installing
==========

View file

@ -22,6 +22,7 @@ Available Types
telegram.chat
telegram.chatadministratorrights
telegram.chatboost
telegram.chatboostadded
telegram.chatboostremoved
telegram.chatboostsource
telegram.chatboostsourcegiftcode

View file

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

View file

@ -77,3 +77,5 @@
.. |reply_quote| replace:: If set to :obj:`True`, the reply is sent as an actual reply to this message. If ``reply_to_message_id`` is passed, this parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
.. |do_quote| replace:: If set to :obj:`True`, the replied message is quoted. For a dict, it must be the output of :meth:`~telegram.Message.build_reply_arguments` to specify exact ``reply_parameters``. If ``reply_to_message_id`` or ``reply_parameters`` are passed, this parameter will be ignored. Default: :obj:`True` in group chats and :obj:`False` in private chats.
.. |non_optional_story_argument| replace:: As of this version, this argument is now required. In accordance with our `stability policy <https://docs.python-telegram-bot.org/en/stable/stability_policy.html>`__, the signature will be kept as optional for now, though they are mandatory and an error will be raised if you don't pass it.

View file

@ -40,6 +40,7 @@ __all__ = (
"Chat",
"ChatAdministratorRights",
"ChatBoost",
"ChatBoostAdded",
"ChatBoostRemoved",
"ChatBoostSource",
"ChatBoostSourceGiftCode",
@ -242,6 +243,7 @@ from ._chat import Chat
from ._chatadministratorrights import ChatAdministratorRights
from ._chatboost import (
ChatBoost,
ChatBoostAdded,
ChatBoostRemoved,
ChatBoostSource,
ChatBoostSourceGiftCode,

View file

@ -5250,10 +5250,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
user_id (:obj:`int`): Unique identifier of the target user.
is_anonymous (:obj:`bool`, optional): Pass :obj:`True`, if the administrator's presence
in the chat is hidden.
can_manage_chat (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
access the chat event log, chat statistics, boost list in channels, see channel
members, report spam messages, see anonymous administrators in supergroups and
ignore slow mode. Implied by any other administrator privilege.
can_manage_chat (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
access the chat event log, get boost list, see hidden supergroup and channel
members, report spam messages and ignore slow mode. Implied by any other
administrator privilege.
.. versionadded:: 13.4
@ -5285,15 +5285,15 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
.. versionadded:: 20.0
can_post_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
post stories in the channel; channels only.
post stories to the chat.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
edit stories posted by other users; channels only.
edit stories posted by other users.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
delete stories posted by other users; channels only.
delete stories posted by other users.
.. versionadded:: 20.6

View file

@ -219,6 +219,16 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
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:: NEXT.VERSION
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:: NEXT.VERSION
Attributes:
id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits
@ -352,6 +362,16 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
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:: NEXT.VERSION
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:: NEXT.VERSION
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
.. _accent colors: https://core.telegram.org/bots/api#accent-colors
@ -364,6 +384,7 @@ class Chat(TelegramObject):
"background_custom_emoji_id",
"bio",
"can_set_sticker_set",
"custom_emoji_sticker_set_name",
"description",
"emoji_status_custom_emoji_id",
"emoji_status_expiration_date",
@ -392,6 +413,7 @@ class Chat(TelegramObject):
"sticker_set_name",
"title",
"type",
"unrestrict_boost_count",
"username",
)
@ -446,6 +468,8 @@ class Chat(TelegramObject):
profile_accent_color_id: Optional[int] = None,
profile_background_custom_emoji_id: Optional[str] = None,
has_visible_history: Optional[bool] = None,
unrestrict_boost_count: Optional[int] = None,
custom_emoji_sticker_set_name: Optional[str] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -493,6 +517,8 @@ class Chat(TelegramObject):
self.background_custom_emoji_id: Optional[str] = background_custom_emoji_id
self.profile_accent_color_id: Optional[int] = profile_accent_color_id
self.profile_background_custom_emoji_id: Optional[str] = profile_background_custom_emoji_id
self.unrestrict_boost_count: Optional[int] = unrestrict_boost_count
self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name
self._id_attrs = (self.id,)

View file

@ -47,9 +47,8 @@ class ChatAdministratorRights(TelegramObject):
Args:
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
log, chat statistics, boost list in channels, see channel members, report spam
messages, see anonymous administrators in supergroups and ignore slow mode.
Implied by any other administrator privilege.
log, get boost list, see hidden supergroup and channel members, report spam messages
and ignore slow mode. Implied by any other administrator privilege.
can_delete_messages (:obj:`bool`): :obj:`True`, if the administrator can delete messages of
other users.
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the administrator can manage video
@ -70,18 +69,24 @@ class ChatAdministratorRights(TelegramObject):
messages of other users.
can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to pin
messages; groups and supergroups only.
can_post_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can post
stories in the channel; channels only.
can_post_stories (:obj:`bool`): :obj:`True`, if the administrator can post
stories to the chat.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can edit
stories posted by other users; channels only.
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can delete
stories posted by other users; channels only.
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_delete_stories (:obj:`bool`): :obj:`True`, if the administrator can delete
stories posted by other users.
.. versionadded:: 20.6
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_manage_topics (:obj:`bool`, optional): :obj:`True`, if the user is allowed
to create, rename, close, and reopen forum topics; supergroups only.
@ -90,9 +95,8 @@ class ChatAdministratorRights(TelegramObject):
Attributes:
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
log, chat statistics, boost list in channels, see channel members, report spam
messages, see anonymous administrators in supergroups and ignore slow mode.
Implied by any other administrator privilege.
log, get boost list, see hidden supergroup and channel members, report spam messages
and ignore slow mode. Implied by any other administrator privilege.
can_delete_messages (:obj:`bool`): :obj:`True`, if the administrator can delete messages of
other users.
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the administrator can manage video
@ -113,18 +117,24 @@ class ChatAdministratorRights(TelegramObject):
messages of other users.
can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to pin
messages; groups and supergroups only.
can_post_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can post
stories in the channel; channels only.
can_post_stories (:obj:`bool`): :obj:`True`, if the administrator can post
stories to the chat.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit
stories posted by other users; channels only.
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can delete
stories posted by other users; channels only.
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_delete_stories (:obj:`bool`): :obj:`True`, if the administrator can delete
stories posted by other users.
.. versionadded:: 20.6
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_manage_topics (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
to create, rename, close, and reopen forum topics; supergroups only.
@ -179,13 +189,19 @@ class ChatAdministratorRights(TelegramObject):
self.can_promote_members: bool = can_promote_members
self.can_change_info: bool = can_change_info
self.can_invite_users: bool = can_invite_users
# Not actually optionals but because of backwards compatability we pretend they are
if can_post_stories is None or can_edit_stories is None or can_delete_stories is None:
raise TypeError(
"As of vNEXT.VERSION can_post_stories, can_edit_stories and can_delete_stories"
" must be set in order to create this object."
)
self.can_post_stories: bool = can_post_stories
self.can_edit_stories: bool = can_edit_stories
self.can_delete_stories: bool = can_delete_stories
# Optionals
self.can_post_messages: Optional[bool] = can_post_messages
self.can_edit_messages: Optional[bool] = can_edit_messages
self.can_pin_messages: Optional[bool] = can_pin_messages
self.can_post_stories: Optional[bool] = can_post_stories
self.can_edit_stories: Optional[bool] = can_edit_stories
self.can_delete_stories: Optional[bool] = can_delete_stories
self.can_manage_topics: Optional[bool] = can_manage_topics
self._id_attrs = (

View file

@ -34,6 +34,39 @@ if TYPE_CHECKING:
from telegram import Bot
class ChatBoostAdded(TelegramObject):
"""
This object represents a service message about a user boosting a chat.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`boost_count` are equal.
.. versionadded:: NEXT.VERSION
Args:
boost_count (:obj:`int`): Number of boosts added by the user.
Attributes:
boost_count (:obj:`int`): Number of boosts added by the user.
"""
__slots__ = ("boost_count",)
def __init__(
self,
boost_count: int,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.boost_count: int = boost_count
self._id_attrs = (self.boost_count,)
self._freeze()
class ChatBoostSource(TelegramObject):
"""
Base class for Telegram ChatBoostSource objects. It can be one of:

View file

@ -197,9 +197,8 @@ class ChatMemberAdministrator(ChatMember):
is allowed to edit administrator privileges of that user.
is_anonymous (:obj:`bool`): :obj:`True`, if the user's
presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator
can access the chat event log, chat statistics, message statistics in
channels, see channel members, see anonymous administrators in supergroups
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
log, get boost list, see hidden supergroup and channel members, report spam messages
and ignore slow mode. Implied by any other administrator privilege.
can_delete_messages (:obj:`bool`): :obj:`True`, if the
administrator can delete messages of other users.
@ -225,18 +224,24 @@ class ChatMemberAdministrator(ChatMember):
messages; channels only.
can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
to pin messages; groups and supergroups only.
can_post_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can post
stories in the channel; channels only.
can_post_stories (:obj:`bool`): :obj:`True`, if the administrator can post
stories to the chat.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can edit
stories posted by other users; channels only.
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can delete
stories posted by other users; channels only.
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_delete_stories (:obj:`bool`): :obj:`True`, if the administrator can delete
stories posted by other users.
.. versionadded:: 20.6
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_manage_topics (:obj:`bool`, optional): :obj:`True`, if the user is allowed
to create, rename, close, and reopen forum topics; supergroups only.
@ -252,9 +257,8 @@ class ChatMemberAdministrator(ChatMember):
is_anonymous (:obj:`bool`): :obj:`True`, if the user's
presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
log, chat statistics, boost list in channels, see channel members, report spam
messages, see anonymous administrators in supergroups and ignore slow mode.
Implied by any other administrator privilege.
log, get boost list, see hidden supergroup and channel members, report spam messages
and ignore slow mode. Implied by any other administrator privilege.
can_delete_messages (:obj:`bool`): :obj:`True`, if the
administrator can delete messages of other users.
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the
@ -279,18 +283,24 @@ class ChatMemberAdministrator(ChatMember):
messages; channels only.
can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
to pin messages; groups and supergroups only.
can_post_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can post
stories in the channel; channels only.
can_post_stories (:obj:`bool`): :obj:`True`, if the administrator can post
stories to the chat.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit
stories posted by other users; channels only.
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_edit_stories (:obj:`bool`): :obj:`True`, if the administrator can edit
stories posted by other users.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can delete
stories posted by other users; channels only.
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_delete_stories (:obj:`bool`): :obj:`True`, if the administrator can delete
stories posted by other users.
.. versionadded:: 20.6
.. versionchanged:: NEXT.VERSION
|non_optional_story_argument|
can_manage_topics (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
to create, rename, close, and reopen forum topics; supergroups only
@ -352,14 +362,21 @@ class ChatMemberAdministrator(ChatMember):
self.can_promote_members: bool = can_promote_members
self.can_change_info: bool = can_change_info
self.can_invite_users: bool = can_invite_users
# Not actually optionals but because of backwards compatability we pretend they are
if can_post_stories is None or can_edit_stories is None or can_delete_stories is None:
raise TypeError(
"As of NEXT.VERSION can_post_stories, can_edit_stories and can_delete_stories "
"must be set in order to create this object."
)
self.can_post_stories: bool = can_post_stories
self.can_edit_stories: bool = can_edit_stories
self.can_delete_stories: bool = can_delete_stories
# Optionals
self.can_post_messages: Optional[bool] = can_post_messages
self.can_edit_messages: Optional[bool] = can_edit_messages
self.can_pin_messages: Optional[bool] = can_pin_messages
self.can_manage_topics: Optional[bool] = can_manage_topics
self.custom_title: Optional[str] = custom_title
self.can_post_stories: Optional[bool] = can_post_stories
self.can_edit_stories: Optional[bool] = can_edit_stories
self.can_delete_stories: Optional[bool] = can_delete_stories
class ChatMemberMember(ChatMember):

View file

@ -24,6 +24,7 @@ from html import escape
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple, TypedDict, Union
from telegram._chat import Chat
from telegram._chatboost import ChatBoostAdded
from telegram._dice import Dice
from telegram._files.animation import Animation
from telegram._files.audio import Audio
@ -521,6 +522,18 @@ class Message(MaybeInaccessibleMessage):
message for forwarded messages
.. versionadded:: 20.8
reply_to_story (:class:`telegram.Story`, optional): For replies to a story, the original
story.
.. versionadded:: NEXT.VERSION
boost_added (:class:`telegram.ChatBoostAdded`, optional): Service message: user boosted
the chat.
.. versionadded:: NEXT.VERSION
sender_boost_count (:obj:`int`, optional): If the sender of the
message boosted the chat, the number of boosts added by the user.
.. versionadded:: NEXT.VERSION
Attributes:
message_id (:obj:`int`): Unique message identifier inside this chat.
@ -787,10 +800,22 @@ class Message(MaybeInaccessibleMessage):
message, the quoted part of the message.
.. versionadded:: 20.8
forward_origin (:class:`telegram.MessageOrigin`, optional): Information about the original
forward_origin (:class:`telegram.MessageOrigin`): Optional. Information about the original
message for forwarded messages
.. versionadded:: 20.8
reply_to_story (:class:`telegram.Story`): Optional. For replies to a story, the original
story.
.. versionadded:: NEXT.VERSION
boost_added (:class:`telegram.ChatBoostAdded`): Optional. Service message: user boosted
the chat.
.. versionadded:: NEXT.VERSION
sender_boost_count (:obj:`int`): Optional. If the sender of the
message boosted the chat, the number of boosts added by the user.
.. 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
@ -807,6 +832,7 @@ class Message(MaybeInaccessibleMessage):
"animation",
"audio",
"author_signature",
"boost_added",
"caption",
"caption_entities",
"channel_chat_created",
@ -857,6 +883,8 @@ class Message(MaybeInaccessibleMessage):
"quote",
"reply_markup",
"reply_to_message",
"reply_to_story",
"sender_boost_count",
"sender_chat",
"sticker",
"story",
@ -953,6 +981,9 @@ class Message(MaybeInaccessibleMessage):
external_reply: Optional["ExternalReplyInfo"] = None,
quote: Optional["TextQuote"] = None,
forward_origin: Optional["MessageOrigin"] = None,
reply_to_story: Optional[Story] = None,
boost_added: Optional[ChatBoostAdded] = None,
sender_boost_count: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -1045,6 +1076,9 @@ class Message(MaybeInaccessibleMessage):
self.external_reply: Optional[ExternalReplyInfo] = external_reply
self.quote: Optional[TextQuote] = quote
self.forward_origin: Optional[MessageOrigin] = forward_origin
self.reply_to_story: Optional[Story] = reply_to_story
self.boost_added: Optional[ChatBoostAdded] = boost_added
self.sender_boost_count: Optional[int] = sender_boost_count
self._effective_attachment = DEFAULT_NONE
@ -1185,6 +1219,8 @@ class Message(MaybeInaccessibleMessage):
data["external_reply"] = ExternalReplyInfo.de_json(data.get("external_reply"), bot)
data["quote"] = TextQuote.de_json(data.get("quote"), bot)
data["forward_origin"] = MessageOrigin.de_json(data.get("forward_origin"), bot)
data["reply_to_story"] = Story.de_json(data.get("reply_to_story"), bot)
data["boost_added"] = ChatBoostAdded.de_json(data.get("boost_added"), bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility

View file

@ -18,24 +18,65 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object related to a Telegram Story."""
from typing import Optional
from typing import TYPE_CHECKING, Optional
from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class Story(TelegramObject):
"""
This object represents a message about a forwarded story in the chat. Currently holds no
information.
This object represents a story.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`chat` and :attr:`id` are equal.
.. versionadded:: 20.5
.. versionchanged:: NEXT.VERSION
Added attributes :attr:`chat` and :attr:`id` and equality based on them.
Args:
chat (:class:`telegram.Chat`): Chat that posted the story.
id (:obj:`int`): Unique identifier for the story in the chat.
Attributes:
chat (:class:`telegram.Chat`): Chat that posted the story.
id (:obj:`int`): Unique identifier for the story in the chat.
"""
__slots__ = ()
__slots__ = (
"chat",
"id",
)
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
def __init__(
self,
chat: Chat,
id: int, # pylint: disable=redefined-builtin
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.chat: Chat = chat
self.id: int = id
self._id_attrs = (self.chat, self.id)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Story"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["chat"] = Chat.de_json(data.get("chat", {}), bot)
return super().de_json(data=data, bot=bot)

View file

@ -142,7 +142,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=0)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=1)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@ -1705,6 +1705,11 @@ class MessageType(StringEnum):
""":obj:`str`: Messages with :attr:`telegram.Message.animation`."""
AUDIO = "audio"
""":obj:`str`: Messages with :attr:`telegram.Message.audio`."""
BOOST_ADDED = "boost_added"
""":obj:`str`: Messages with :attr:`telegram.Message.boost_added`.
.. versionadded:: NEXT.VERSION
"""
CHANNEL_CHAT_CREATED = "channel_chat_created"
""":obj:`str`: Messages with :attr:`telegram.Message.channel_chat_created`."""
CHAT_SHARED = "chat_shared"
@ -1802,6 +1807,16 @@ class MessageType(StringEnum):
""":obj:`str`: Messages with :attr:`telegram.Message.poll`."""
PROXIMITY_ALERT_TRIGGERED = "proximity_alert_triggered"
""":obj:`str`: Messages with :attr:`telegram.Message.proximity_alert_triggered`."""
REPLY_TO_STORY = "reply_to_story"
""":obj:`str`: Messages with :attr:`telegram.Message.reply_to_story`.
.. versionadded:: NEXT.VERSION
"""
SENDER_BOOST_COUNT = "sender_boost_count"
""":obj:`str`: Messages with :attr:`telegram.Message.sender_boost_count`.
.. versionadded:: NEXT.VERSION
"""
STICKER = "sticker"
""":obj:`str`: Messages with :attr:`telegram.Message.sticker`."""
STORY = "story"

View file

@ -41,6 +41,7 @@ __all__ = (
"ANIMATION",
"ATTACHMENT",
"AUDIO",
"BOOST_ADDED",
"CAPTION",
"CHAT",
"COMMAND",
@ -60,6 +61,8 @@ __all__ = (
"POLL",
"PREMIUM_USER",
"REPLY",
"REPLY_TO_STORY",
"SENDER_BOOST_COUNT",
"STORY",
"SUCCESSFUL_PAYMENT",
"TEXT",
@ -2789,3 +2792,36 @@ class _Voice(MessageFilter):
VOICE = _Voice("filters.VOICE")
"""Messages that contain :attr:`telegram.Message.voice`."""
class _ReplyToStory(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.reply_to_story)
REPLY_TO_STORY = _ReplyToStory(name="filters.REPLY_TO_STORY")
"""Messages that contain :attr:`telegram.Message.reply_to_story`."""
class _BoostAdded(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.boost_added)
BOOST_ADDED = _BoostAdded(name="filters.BOOST_ADDED")
"""Messages that contain :attr:`telegram.Message.boost_added`."""
class _SenderBoostCount(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.sender_boost_count)
SENDER_BOOST_COUNT = _SenderBoostCount(name="filters.SENDER_BOOST_COUNT")
"""Messages that contain :attr:`telegram.Message.sender_boost_count`."""

View file

@ -2700,3 +2700,24 @@ class TestFilters:
update.message.giveaway_winners = "test"
assert filters.GIVEAWAY_WINNERS.check_update(update)
assert str(filters.GIVEAWAY_WINNERS) == "filters.GIVEAWAY_WINNERS"
def test_filters_reply_to_story(self, update):
assert not filters.REPLY_TO_STORY.check_update(update)
update.message.reply_to_story = "test"
assert filters.REPLY_TO_STORY.check_update(update)
assert str(filters.REPLY_TO_STORY) == "filters.REPLY_TO_STORY"
def test_filters_boost_added(self, update):
assert not filters.BOOST_ADDED.check_update(update)
update.message.boost_added = "test"
assert filters.BOOST_ADDED.check_update(update)
assert str(filters.BOOST_ADDED) == "filters.BOOST_ADDED"
def test_filters_sender_boost_count(self, update):
assert not filters.SENDER_BOOST_COUNT.check_update(update)
update.message.sender_boost_count = "test"
assert filters.SENDER_BOOST_COUNT.check_update(update)
assert str(filters.SENDER_BOOST_COUNT) == "filters.SENDER_BOOST_COUNT"

View file

@ -72,6 +72,8 @@ def chat(bot):
background_custom_emoji_id=TestChatBase.background_custom_emoji_id,
profile_accent_color_id=TestChatBase.profile_accent_color_id,
profile_background_custom_emoji_id=TestChatBase.profile_background_custom_emoji_id,
unrestrict_boost_count=TestChatBase.unrestrict_boost_count,
custom_emoji_sticker_set_name=TestChatBase.custom_emoji_sticker_set_name,
)
chat.set_bot(bot)
chat._unfreeze()
@ -115,6 +117,8 @@ class TestChatBase:
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"
class TestChatWithoutRequest(TestChatBase):
@ -156,6 +160,8 @@ class TestChatWithoutRequest(TestChatBase):
"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,
}
chat = Chat.de_json(json_dict, bot)
@ -194,6 +200,8 @@ class TestChatWithoutRequest(TestChatBase):
assert chat.background_custom_emoji_id == self.background_custom_emoji_id
assert chat.profile_accent_color_id == self.profile_accent_color_id
assert chat.profile_background_custom_emoji_id == self.profile_background_custom_emoji_id
assert chat.unrestrict_boost_count == self.unrestrict_boost_count
assert chat.custom_emoji_sticker_set_name == self.custom_emoji_sticker_set_name
def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = {
@ -257,6 +265,8 @@ class TestChatWithoutRequest(TestChatBase):
chat_dict["profile_background_custom_emoji_id"]
== chat.profile_background_custom_emoji_id
)
assert chat_dict["custom_emoji_sticker_set_name"] == chat.custom_emoji_sticker_set_name
assert chat_dict["unrestrict_boost_count"] == chat.unrestrict_boost_count
def test_always_tuples_attributes(self):
chat = Chat(

View file

@ -95,11 +95,42 @@ class TestChatAdministratorRightsWithoutRequest:
assert admin_rights_dict["can_delete_stories"] == car.can_delete_stories
def test_equality(self):
a = ChatAdministratorRights(True, *((False,) * 11))
b = ChatAdministratorRights(True, *((False,) * 11))
c = ChatAdministratorRights(*(False,) * 12)
d = ChatAdministratorRights(True, True, *((False,) * 10))
e = ChatAdministratorRights(True, True, *((False,) * 10))
a = ChatAdministratorRights(
True,
*((False,) * 11),
can_post_stories=False,
can_edit_stories=False,
can_delete_stories=False,
)
b = ChatAdministratorRights(
True,
*((False,) * 11),
can_post_stories=False,
can_edit_stories=False,
can_delete_stories=False,
)
c = ChatAdministratorRights(
*(False,) * 12,
can_post_stories=False,
can_edit_stories=False,
can_delete_stories=False,
)
d = ChatAdministratorRights(
True,
True,
*((False,) * 10),
can_post_stories=False,
can_edit_stories=False,
can_delete_stories=False,
)
e = ChatAdministratorRights(
True,
True,
*((False,) * 10),
can_post_stories=False,
can_edit_stories=False,
can_delete_stories=False,
)
assert a == b
assert hash(a) == hash(b)
@ -115,7 +146,20 @@ class TestChatAdministratorRightsWithoutRequest:
assert hash(d) == hash(e)
def test_all_rights(self):
f = ChatAdministratorRights(True, True, True, True, True, True, True, True, True)
f = ChatAdministratorRights(
True,
True,
True,
True,
True,
True,
True,
True,
True,
can_post_stories=True,
can_edit_stories=True,
can_delete_stories=True,
)
t = ChatAdministratorRights.all_rights()
# if the dirs are the same, the attributes will all be there
assert dir(f) == dir(t)
@ -127,7 +171,20 @@ class TestChatAdministratorRightsWithoutRequest:
assert f != t
def test_no_rights(self):
f = ChatAdministratorRights(False, False, False, False, False, False, False, False, False)
f = ChatAdministratorRights(
False,
False,
False,
False,
False,
False,
False,
False,
False,
can_post_stories=False,
can_edit_stories=False,
can_delete_stories=False,
)
t = ChatAdministratorRights.no_rights()
# if the dirs are the same, the attributes will all be there
assert dir(f) == dir(t)
@ -137,3 +194,19 @@ class TestChatAdministratorRightsWithoutRequest:
assert t[key] is False
# and as a finisher, make sure the default is different.
assert f != t
def test_depreciation_typeerror(self):
with pytest.raises(TypeError, match="must be set in order"):
ChatAdministratorRights(
*(False,) * 12,
)
with pytest.raises(TypeError, match="must be set in order"):
ChatAdministratorRights(*(False,) * 12, can_edit_stories=True)
with pytest.raises(TypeError, match="must be set in order"):
ChatAdministratorRights(*(False,) * 12, can_post_stories=True)
with pytest.raises(TypeError, match="must be set in order"):
ChatAdministratorRights(*(False,) * 12, can_delete_stories=True)
with pytest.raises(TypeError, match="must be set in order"):
ChatAdministratorRights(*(False,) * 12, can_edit_stories=True, can_post_stories=True)
with pytest.raises(TypeError, match="must be set in order"):
ChatAdministratorRights(*(False,) * 12, can_delete_stories=True, can_post_stories=True)

View file

@ -24,6 +24,7 @@ import pytest
from telegram import (
Chat,
ChatBoost,
ChatBoostAdded,
ChatBoostRemoved,
ChatBoostSource,
ChatBoostSourceGiftCode,
@ -542,3 +543,42 @@ class TestUserChatBoostsWithRequest(ChatBoostDefaults):
async def test_get_user_chat_boosts(self, bot, channel_id, chat_id):
chat_boosts = await bot.get_user_chat_boosts(channel_id, chat_id)
assert isinstance(chat_boosts, UserChatBoosts)
class TestChatBoostAddedWithoutRequest:
boost_count = 100
def test_slot_behaviour(self):
action = ChatBoostAdded(8)
for attr in action.__slots__:
assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
def test_de_json(self):
json_dict = {"boost_count": self.boost_count}
chat_boost_added = ChatBoostAdded.de_json(json_dict, None)
assert chat_boost_added.api_kwargs == {}
assert chat_boost_added.boost_count == self.boost_count
def test_to_dict(self):
chat_boost_added = ChatBoostAdded(self.boost_count)
chat_boost_added_dict = chat_boost_added.to_dict()
assert isinstance(chat_boost_added_dict, dict)
assert chat_boost_added_dict["boost_count"] == self.boost_count
def test_equality(self):
a = ChatBoostAdded(100)
b = ChatBoostAdded(100)
c = ChatBoostAdded(50)
d = Chat(1, "")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -150,8 +150,13 @@ def make_json_dict(instance: ChatMember, include_optional_args: bool = False) ->
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:
# If we want to test all args (for de_json)
# or if the param is optional but for backwards compatability
elif (
param.default is not inspect.Parameter.empty
and include_optional_args
or param.name in ["can_delete_stories", "can_post_stories", "can_edit_stories"]
):
json_dict[param.name] = val
return json_dict
@ -297,3 +302,19 @@ class TestChatMemberTypesWithoutRequest:
assert c != e
assert hash(c) != hash(e)
def test_deprecation_typeerror(self, chat_member_type):
with pytest.raises(TypeError, match="must be set in order"):
ChatMemberAdministrator(
*(False,) * 12,
)
with pytest.raises(TypeError, match="must be set in order"):
ChatMemberAdministrator(*(False,) * 12, can_edit_stories=True)
with pytest.raises(TypeError, match="must be set in order"):
ChatMemberAdministrator(*(False,) * 12, can_post_stories=True)
with pytest.raises(TypeError, match="must be set in order"):
ChatMemberAdministrator(*(False,) * 12, can_delete_stories=True)
with pytest.raises(TypeError, match="must be set in order"):
ChatMemberAdministrator(*(False,) * 12, can_edit_stories=True, can_post_stories=True)
with pytest.raises(TypeError, match="must be set in order"):
ChatMemberAdministrator(*(False,) * 12, can_delete_stories=True, can_post_stories=True)

View file

@ -64,6 +64,9 @@ def new_chat_member(user):
True,
True,
True,
can_post_stories=True,
can_edit_stories=True,
can_delete_stories=True,
)
@ -264,10 +267,19 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
@pytest.mark.parametrize(
"optional_attribute",
# This gives the names of all optional arguments of ChatMember
# skipping stories names because they aren't optional even though we pretend they are
[
name
for name, param in inspect.signature(ChatMemberAdministrator).parameters.items()
if name not in ["self", "api_kwargs"] and param.default != inspect.Parameter.empty
if name
not in [
"self",
"api_kwargs",
"can_delete_stories",
"can_post_stories",
"can_edit_stories",
]
and param.default != inspect.Parameter.empty
],
)
def test_difference_optionals(self, optional_attribute, user, chat):
@ -276,8 +288,22 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
old_value = "old_value"
new_value = "new_value"
trues = tuple(True for _ in range(9))
old_chat_member = ChatMemberAdministrator(user, *trues, **{optional_attribute: old_value})
new_chat_member = ChatMemberAdministrator(user, *trues, **{optional_attribute: new_value})
old_chat_member = ChatMemberAdministrator(
user,
*trues,
**{optional_attribute: old_value},
can_delete_stories=True,
can_edit_stories=True,
can_post_stories=True,
)
new_chat_member = ChatMemberAdministrator(
user,
*trues,
**{optional_attribute: new_value},
can_delete_stories=True,
can_edit_stories=True,
can_post_stories=True,
)
chat_member_updated = ChatMemberUpdated(
chat, user, datetime.datetime.utcnow(), old_chat_member, new_chat_member
)

View file

@ -106,10 +106,30 @@ class TestKeyboardButtonRequestChatBase:
chat_has_username = True
chat_is_created = False
user_administrator_rights = ChatAdministratorRights(
True, False, True, False, True, False, True, False
True,
False,
True,
False,
True,
False,
True,
False,
can_post_stories=False,
can_edit_stories=False,
can_delete_stories=False,
)
bot_administrator_rights = ChatAdministratorRights(
True, False, True, False, True, False, True, False
True,
False,
True,
False,
True,
False,
True,
False,
can_post_stories=False,
can_edit_stories=False,
can_delete_stories=False,
)
bot_is_member = True

View file

@ -26,6 +26,7 @@ from telegram import (
Audio,
Bot,
Chat,
ChatBoostAdded,
ChatShared,
Contact,
Dice,
@ -129,7 +130,7 @@ def message(bot):
},
{"photo": [PhotoSize("photo_id", "unique_id", 50, 50)], "caption": "photo_file"},
{"sticker": Sticker("sticker_id", "unique_id", 50, 50, True, False, Sticker.REGULAR)},
{"story": Story()},
{"story": Story(Chat(1, Chat.PRIVATE), 0)},
{"video": Video("video_id", "unique_id", 12, 12, 12), "caption": "video_file"},
{"voice": Voice("voice_id", "unique_id", 5)},
{"video_note": VideoNote("video_note_id", "unique_id", 20, 12)},
@ -259,6 +260,9 @@ def message(bot):
},
{"quote": TextQuote("a text quote", 1)},
{"forward_origin": MessageOriginChat(datetime.utcnow(), Chat(1, Chat.PRIVATE))},
{"reply_to_story": Story(Chat(1, Chat.PRIVATE), 0)},
{"boost_added": ChatBoostAdded(100)},
{"sender_boost_count": 1},
],
ids=[
"reply",
@ -321,6 +325,9 @@ def message(bot):
"external_reply",
"quote",
"forward_origin",
"reply_to_story",
"boost_added",
"sender_boost_count",
],
)
def message_params(bot, request):

View file

@ -132,6 +132,9 @@ def ptb_extra_params(object_name: str) -> set[str]:
# Mostly due to the value being fixed anyway
PTB_IGNORED_PARAMS = {
r"InlineQueryResult\w+": {"type"},
# TODO: Remove this in vNEXT.VERSION (API 7.1) when this can stop being optional
r"ChatAdministratorRights": {"can_post_stories", "can_edit_stories", "can_delete_stories"},
r"ChatMemberAdministrator": {"can_post_stories", "can_edit_stories", "can_delete_stories"},
r"ChatMember\w+": {"status"},
r"PassportElementError\w+": {"source"},
"ForceReply": {"force_reply"},
@ -166,7 +169,11 @@ 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]] = {
# TODO: Remove this in vNEXT.VERSION (API 7.1) when this can stop being optional
r"ChatAdministratorRights": {"can_post_stories", "can_edit_stories", "can_delete_stories"},
r"ChatMemberAdministrator": {"can_post_stories", "can_edit_stories", "can_delete_stories"},
}
def backwards_compat_kwargs(object_name: str) -> set[str]:

View file

@ -18,28 +18,55 @@
import pytest
from telegram import Story
from telegram import Chat, Story
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def story():
return Story()
return Story(TestStoryBase.chat, TestStoryBase.id)
class TestStoryWithoutRequest:
def test_slot_behaviour(self):
story = Story()
class TestStoryBase:
chat = Chat(1, "")
id = 0
class TestStoryWithoutRequest(TestStoryBase):
def test_slot_behaviour(self, story):
for attr in story.__slots__:
assert getattr(story, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(story)) == len(set(mro_slots(story))), "duplicate slot"
def test_de_json(self):
story = Story.de_json({}, None)
def test_de_json(self, bot):
json_dict = {"chat": self.chat.to_dict(), "id": self.id}
story = Story.de_json(json_dict, bot)
assert story.api_kwargs == {}
assert story.chat == self.chat
assert story.id == self.id
assert isinstance(story, Story)
assert Story.de_json(None, bot) is None
def test_to_dict(self):
story = Story()
def test_to_dict(self, story):
story_dict = story.to_dict()
assert story_dict == {}
assert story_dict["chat"] == self.chat.to_dict()
assert story_dict["id"] == self.id
def test_equality(self):
a = Story(Chat(1, ""), 0)
b = Story(Chat(1, ""), 0)
c = Story(Chat(1, ""), 1)
d = Story(Chat(2, ""), 0)
e = Chat(1, "")
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)