Co-authored-by: Aditya <clot27@apx_managed.vanilla>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
This commit is contained in:
Harshil 2023-09-03 15:43:44 +04:00 committed by GitHub
parent caffb9d66e
commit bd24da29cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 446 additions and 38 deletions

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-6.7-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.8-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 **6.7** are supported.
All types and methods of the Telegram Bot API **6.8** 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-6.7-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.8-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 **6.7** are supported.
All types and methods of the Telegram Bot API **6.8** are supported.
Installing
==========

View file

@ -328,6 +328,8 @@
- Used to reopen the general topic
* - :meth:`~telegram.Bot.unpin_all_forum_topic_messages`
- Used to unpin all messages in a forum topic
* - :meth:`~telegram.Bot.unpin_all_general_forum_topic_messages`
- Used to unpin all messages in the general forum topic
.. raw:: html

View file

@ -79,6 +79,7 @@ Available Types
telegram.replykeyboardmarkup
telegram.replykeyboardremove
telegram.sentwebappmessage
telegram.story
telegram.switchinlinequerychosenchat
telegram.telegramobject
telegram.update

View file

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

View file

@ -170,6 +170,7 @@ __all__ = ( # Keep this alphabetically ordered
"ShippingQuery",
"Sticker",
"StickerSet",
"Story",
"SuccessfulPayment",
"SwitchInlineQueryChosenChat",
"TelegramObject",
@ -341,6 +342,7 @@ from ._replykeyboardmarkup import ReplyKeyboardMarkup
from ._replykeyboardremove import ReplyKeyboardRemove
from ._sentwebappmessage import SentWebAppMessage
from ._shared import ChatShared, UserShared
from ._story import Story
from ._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
from ._telegramobject import TelegramObject
from ._update import Update

View file

@ -7814,6 +7814,46 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs=api_kwargs,
)
@_log
async def unpin_all_general_forum_topic_messages(
self,
chat_id: Union[str, int],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""
Use this method to clear the list of pinned messages in a General forum topic. The bot must
be an administrator in the chat for this to work and must have
:paramref:`~telegram.ChatAdministratorRights.can_pin_messages` administrator rights in the
supergroup.
.. versionadded:: NEXT.VERSION
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id}
return await self._post(
"unpinAllGeneralForumTopicMessages",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
@_log
async def edit_general_forum_topic(
self,
@ -8527,3 +8567,5 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""Alias for :meth:`set_my_name`"""
getMyName = get_my_name
"""Alias for :meth:`get_my_name`"""
unpinAllGeneralForumTopicMessages = unpin_all_general_forum_topic_messages
"""Alias for :meth:`unpin_all_general_forum_topic_messages`"""

View file

@ -31,6 +31,7 @@ from telegram._menubutton import MenuButton
from telegram._telegramobject import TelegramObject
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.defaultvalue import DEFAULT_NONE
from telegram._utils.types import (
CorrectOptionID,
@ -172,6 +173,12 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
emoji_status_expiration_date (:class:`datetime.datetime`, optional): Expiration date of
emoji status of the other party in a private chat, in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
|datetime_localization|
.. versionadded:: NEXT.VERSION
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`.
@ -265,6 +272,12 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
emoji_status_expiration_date (:class:`datetime.datetime`, optional): Expiration date of
emoji status of the other party in a private chat, in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
|datetime_localization|
.. versionadded:: NEXT.VERSION
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`.
@ -306,6 +319,7 @@ class Chat(TelegramObject):
"is_forum",
"active_usernames",
"emoji_status_custom_emoji_id",
"emoji_status_expiration_date",
"has_hidden_members",
"has_aggressive_anti_spam_enabled",
)
@ -352,6 +366,7 @@ class Chat(TelegramObject):
is_forum: Optional[bool] = None,
active_usernames: Optional[Sequence[str]] = None,
emoji_status_custom_emoji_id: Optional[str] = None,
emoji_status_expiration_date: Optional[datetime] = None,
has_aggressive_anti_spam_enabled: Optional[bool] = None,
has_hidden_members: Optional[bool] = None,
*,
@ -390,6 +405,7 @@ class Chat(TelegramObject):
self.is_forum: Optional[bool] = is_forum
self.active_usernames: Tuple[str, ...] = parse_sequence_arg(active_usernames)
self.emoji_status_custom_emoji_id: Optional[str] = emoji_status_custom_emoji_id
self.emoji_status_expiration_date: Optional[datetime] = emoji_status_expiration_date
self.has_aggressive_anti_spam_enabled: Optional[bool] = has_aggressive_anti_spam_enabled
self.has_hidden_members: Optional[bool] = has_hidden_members
@ -446,6 +462,13 @@ class Chat(TelegramObject):
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["emoji_status_expiration_date"] = from_timestamp(
data.get("emoji_status_expiration_date"), tzinfo=loc_tzinfo
)
data["photo"] = ChatPhoto.de_json(data.get("photo"), bot)
from telegram import Message # pylint: disable=import-outside-toplevel
@ -2904,6 +2927,37 @@ class Chat(TelegramObject):
api_kwargs=api_kwargs,
)
async def unpin_all_general_forum_topic_messages(
self,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""Shortcut for::
await bot.unpin_all_general_forum_topic_messages(chat_id=update.effective_chat.id,
*args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.unpin_all_general_forum_topic_messages`.
.. versionadded:: NEXT.VERSION
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().unpin_all_general_forum_topic_messages(
chat_id=self.id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def edit_general_forum_topic(
self,
name: str,

View file

@ -53,6 +53,7 @@ from telegram._payment.successfulpayment import SuccessfulPayment
from telegram._poll import Poll
from telegram._proximityalerttriggered import ProximityAlertTriggered
from telegram._shared import ChatShared, UserShared
from telegram._story import Story
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
@ -201,6 +202,9 @@ class Message(TelegramObject):
sticker (:class:`telegram.Sticker`, optional): Message is a sticker, information
about the sticker.
story (:class:`telegram.Story`, optional): Message is a forwarded story.
.. versionadded:: NEXT.VERSION
video (:class:`telegram.Video`, optional): Message is a video, information about the
video.
voice (:class:`telegram.Voice`, optional): Message is a voice message, information about
@ -435,6 +439,9 @@ class Message(TelegramObject):
about the sticker.
.. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>`
story (:class:`telegram.Story`): Optional. Message is a forwarded story.
.. versionadded:: NEXT.VERSION
video (:class:`telegram.Video`): Optional. Message is a video, information about the
video.
@ -671,6 +678,7 @@ class Message(TelegramObject):
"has_media_spoiler",
"user_shared",
"chat_shared",
"story",
)
def __init__(
@ -746,6 +754,7 @@ class Message(TelegramObject):
has_media_spoiler: Optional[bool] = None,
user_shared: Optional[UserShared] = None,
chat_shared: Optional[ChatShared] = None,
story: Optional[Story] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -834,6 +843,7 @@ class Message(TelegramObject):
self.has_media_spoiler: Optional[bool] = has_media_spoiler
self.user_shared: Optional[UserShared] = user_shared
self.chat_shared: Optional[ChatShared] = chat_shared
self.story: Optional[Story] = story
self._effective_attachment = DEFAULT_NONE
@ -903,6 +913,7 @@ class Message(TelegramObject):
data["game"] = Game.de_json(data.get("game"), bot)
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
data["story"] = Story.de_json(data.get("story"), bot)
data["video"] = Video.de_json(data.get("video"), bot)
data["voice"] = Voice.de_json(data.get("voice"), bot)
data["video_note"] = VideoNote.de_json(data.get("video_note"), bot)
@ -973,6 +984,7 @@ class Message(TelegramObject):
Sequence[PhotoSize],
Poll,
Sticker,
Story,
SuccessfulPayment,
Venue,
Video,
@ -995,6 +1007,7 @@ class Message(TelegramObject):
* List[:class:`telegram.PhotoSize`]
* :class:`telegram.Poll`
* :class:`telegram.Sticker`
* :class:`telegram.Story`
* :class:`telegram.SuccessfulPayment`
* :class:`telegram.Venue`
* :class:`telegram.Video`

View file

@ -21,6 +21,7 @@ import datetime
from typing import TYPE_CHECKING, Dict, Final, List, Optional, Sequence, Tuple
from telegram import constants
from telegram._chat import Chat
from telegram._messageentity import MessageEntity
from telegram._telegramobject import TelegramObject
from telegram._user import User
@ -28,6 +29,8 @@ 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.warnings import warn
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import Bot
@ -84,42 +87,86 @@ class PollAnswer(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`poll_id`, :attr:`user` and :attr:`option_ids` are equal.
.. versionchanged:: NEXT.VERSION
The order of :paramref:`option_ids` and :paramref:`user` is changed in
NEXT.VERSION as the latter one became optional. We currently provide
backward compatibility for this but it will be removed in the future.
Please update your code to use the new order.
Args:
poll_id (:obj:`str`): Unique poll identifier.
user (:class:`telegram.User`): The user, who changed the answer to the poll.
option_ids (Sequence[:obj:`int`]): 0-based identifiers of answer options, chosen by the
user. May be empty if the user retracted their vote.
option_ids (Sequence[:obj:`int`]): Identifiers of answer options, chosen by the user. May
be empty if the user retracted their vote.
.. versionchanged:: 20.0
|sequenceclassargs|
user (:class:`telegram.User`, optional): The user that changed the answer to the poll,
if the voter isn't anonymous. If the voter is anonymous, this field will contain the
user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility.
.. versionchanged:: NEXT.VERSION
:paramref:`user` became optional.
voter_chat (:class:`telegram.Chat`, optional): The chat that changed the answer to the
poll, if the voter is anonymous.
.. versionadded:: NEXT.VERSION
Attributes:
poll_id (:obj:`str`): Unique poll identifier.
user (:class:`telegram.User`): The user, who changed the answer to the poll.
option_ids (Tuple[:obj:`int`]): Identifiers of answer options, chosen by the user. May be
empty if the user retracted their vote.
option_ids (Tuple[:obj:`int`]): Identifiers of answer options, chosen by the user. May
be empty if the user retracted their vote.
.. versionchanged:: 20.0
|tupleclassattrs|
user (:class:`telegram.User`): Optional. The user, who changed the answer to the
poll, if the voter isn't anonymous. If the voter is anonymous, this field will contain
the user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility
.. versionchanged:: NEXT.VERSION
:paramref:`user` became optional.
voter_chat (:class:`telegram.Chat`): Optional. The chat that changed the answer to the
poll, if the voter is anonymous.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("option_ids", "user", "poll_id")
__slots__ = ("option_ids", "poll_id", "user", "voter_chat")
def __init__(
self,
poll_id: str,
user: User,
option_ids: Sequence[int],
user: Optional[User] = None,
voter_chat: Optional[Chat] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.poll_id: str = poll_id
self.user: User = user
self.option_ids: Tuple[int, ...] = parse_sequence_arg(option_ids)
self.voter_chat: Optional[Chat] = voter_chat
self._id_attrs = (self.poll_id, self.user, tuple(self.option_ids))
if isinstance(option_ids, User) or isinstance(user, tuple):
warn(
"From v20.5 the order of `option_ids` and `user` is changed as the latter one"
" became optional. Please update your code to use the new order.",
category=PTBDeprecationWarning,
stacklevel=2,
)
self.option_ids: Tuple[int, ...] = parse_sequence_arg(user)
self.user: Optional[User] = option_ids
else:
self.option_ids: Tuple[int, ...] = parse_sequence_arg( # type: ignore[no-redef]
option_ids
)
self.user: Optional[User] = user # type: ignore[no-redef]
self._id_attrs = (
self.poll_id,
self.option_ids,
self.user,
self.voter_chat,
)
self._freeze()
@ -132,6 +179,7 @@ class PollAnswer(TelegramObject):
return None
data["user"] = User.de_json(data.get("user"), bot)
data["voter_chat"] = Chat.de_json(data.get("voter_chat"), bot)
return super().de_json(data=data, bot=bot)

41
telegram/_story.py Normal file
View file

@ -0,0 +1,41 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# 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 related to a Telegram Story."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class Story(TelegramObject):
"""
This object represents a message about a forwarded story in the chat. Currently holds no
information.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(api_kwargs=api_kwargs)
self._freeze()

View file

@ -116,7 +116,7 @@ class _BotAPIVersion(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=6, minor=7)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=6, minor=8)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@ -285,7 +285,8 @@ class ChatID(IntEnum):
__slots__ = ()
ANONYMOUS_ADMIN = 1087968824
""":obj:`int`: User ID in groups for messages sent by anonymous admins.
""":obj:`int`: User ID in groups for messages sent by anonymous admins. Telegram chat:
`@GroupAnonymousBot <https://t.me/GroupAnonymousBot>`_.
Note:
:attr:`telegram.Message.from_user` will contain this ID for backwards compatibility only.
@ -293,19 +294,21 @@ class ChatID(IntEnum):
"""
SERVICE_CHAT = 777000
""":obj:`int`: Telegram service chat, that also acts as sender of channel posts forwarded to
discussion groups.
discussion groups. Telegram chat: `Telegram <https://t.me/+42777>`_.
Note:
:attr:`telegram.Message.from_user` will contain this ID for backwards compatibility only.
It's recommended to use :attr:`telegram.Message.sender_chat` instead.
"""
FAKE_CHANNEL = 136817688
""":obj:`int`: User ID in groups when message is sent on behalf of a channel.
""":obj:`int`: User ID in groups when message is sent on behalf of a channel, or when a channel
votes on a poll. Telegram chat: `@Channel_Bot <https://t.me/Channel_Bot>`_.
Note:
* :attr:`telegram.Message.from_user` will contain this ID for backwards compatibility only.
It's recommended to use :attr:`telegram.Message.sender_chat` instead.
* This value is undocumented and might be changed by Telegram.
* :attr:`telegram.PollAnswer.user` will contain this ID for backwards compatibility only.
It's recommended to use :attr:`telegram.PollAnswer.voter_chat` instead.
"""
@ -1065,6 +1068,8 @@ class MessageAttachmentType(StringEnum):
""":obj:`str`: Messages with :attr:`telegram.Message.poll`."""
STICKER = "sticker"
""":obj:`str`: Messages with :attr:`telegram.Message.sticker`."""
STORY = "story"
""":obj:`str`: Messages with :attr:`telegram.Message.story`."""
SUCCESSFUL_PAYMENT = "successful_payment"
""":obj:`str`: Messages with :attr:`telegram.Message.successful_payment`."""
VIDEO = "video"
@ -1219,6 +1224,8 @@ class MessageType(StringEnum):
""":obj:`str`: Messages with :attr:`telegram.Message.poll`."""
STICKER = "sticker"
""":obj:`str`: Messages with :attr:`telegram.Message.sticker`."""
STORY = "story"
""":obj:`str`: Messages with :attr:`telegram.Message.story`."""
SUCCESSFUL_PAYMENT = "successful_payment"
""":obj:`str`: Messages with :attr:`telegram.Message.successful_payment`."""
VIDEO = "video"

View file

@ -3486,6 +3486,26 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def unpin_all_general_forum_topic_messages(
self,
chat_id: Union[str, int],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
rate_limit_args: Optional[RLARGS] = None,
) -> bool:
return await super().unpin_all_general_forum_topic_messages(
chat_id=chat_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def upload_sticker_file(
self,
user_id: Union[str, int],
@ -3884,3 +3904,4 @@ class ExtBot(Bot, Generic[RLARGS]):
setStickerMaskPosition = set_sticker_mask_position
setMyName = set_my_name
getMyName = get_my_name
unpinAllGeneralForumTopicMessages = unpin_all_general_forum_topic_messages

View file

@ -72,6 +72,7 @@ __all__ = (
"REPLY",
"Regex",
"Sticker",
"STORY",
"SUCCESSFUL_PAYMENT",
"SenderChat",
"StatusUpdate",
@ -2143,6 +2144,20 @@ class Sticker:
# neither mask nor emoji can be a message.sticker, so no filters for them
class _Story(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.story)
STORY = _Story(name="filters.STORY")
"""Messages that contain :attr:`telegram.Message.story`.
.. versionadded:: NEXT.VERSION
"""
class _SuccessfulPayment(MessageFilter):
__slots__ = ()

View file

@ -33,8 +33,6 @@ class TestFiles:
@pytest.mark.parametrize(
("string", "expected"),
[
(str(data_file("game.gif")), True),
(str(TEST_DATA_PATH), False),
(str(data_file("game.gif")), True),
(str(TEST_DATA_PATH), False),
(data_file("game.gif"), True),

View file

@ -884,6 +884,11 @@ class TestFilters:
assert filters.Sticker.VIDEO.check_update(update)
assert filters.Sticker.PREMIUM.check_update(update)
def test_filters_story(self, update):
assert not filters.STORY.check_update(update)
update.message.story = "test"
assert filters.STORY.check_update(update)
def test_filters_video(self, update):
assert not filters.VIDEO.check_update(update)
update.message.video = "test"

View file

@ -69,7 +69,7 @@ def false_update(request):
@pytest.fixture()
def poll_answer(bot):
return Update(0, poll_answer=PollAnswer(1, User(2, "test user", False), [0, 1]))
return Update(0, poll_answer=PollAnswer(1, [0, 1], User(2, "test user", False), Chat(1, "")))
class TestPollAnswerHandler:

View file

@ -16,10 +16,12 @@
#
# 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 time
import pytest
from telegram import Bot, Chat, ChatLocation, ChatPermissions, Location, User
from telegram._utils.datetime import UTC, from_timestamp
from telegram.constants import ChatAction, ChatType
from telegram.helpers import escape_markdown
from tests.auxil.bot_method_checks import (
@ -52,6 +54,7 @@ def chat(bot):
is_forum=True,
active_usernames=TestChatBase.active_usernames,
emoji_status_custom_emoji_id=TestChatBase.emoji_status_custom_emoji_id,
emoji_status_expiration_date=TestChatBase.emoji_status_expiration_date,
has_aggressive_anti_spam_enabled=TestChatBase.has_aggressive_anti_spam_enabled,
has_hidden_members=TestChatBase.has_hidden_members,
)
@ -85,6 +88,7 @@ class TestChatBase:
is_forum = True
active_usernames = ["These", "Are", "Usernames!"]
emoji_status_custom_emoji_id = "VeryUniqueCustomEmojiID"
emoji_status_expiration_date = time.time()
has_aggressive_anti_spam_enabled = True
has_hidden_members = True
@ -119,6 +123,7 @@ class TestChatWithoutRequest(TestChatBase):
"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": self.emoji_status_expiration_date,
"has_aggressive_anti_spam_enabled": self.has_aggressive_anti_spam_enabled,
"has_hidden_members": self.has_hidden_members,
}
@ -150,9 +155,32 @@ class TestChatWithoutRequest(TestChatBase):
assert chat.is_forum == self.is_forum
assert chat.active_usernames == tuple(self.active_usernames)
assert chat.emoji_status_custom_emoji_id == self.emoji_status_custom_emoji_id
assert chat.emoji_status_expiration_date == from_timestamp(
self.emoji_status_expiration_date
)
assert chat.has_aggressive_anti_spam_enabled == self.has_aggressive_anti_spam_enabled
assert chat.has_hidden_members == self.has_hidden_members
def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = {
"id": self.id_,
"type": self.type_,
"emoji_status_expiration_date": self.emoji_status_expiration_date,
}
chat_bot = Chat.de_json(json_dict, bot)
chat_bot_raw = Chat.de_json(json_dict, raw_bot)
chat_bot_tz = Chat.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing tzinfo objects is not reliable
emoji_expire_offset = chat_bot_tz.emoji_status_expiration_date.utcoffset()
emoji_expire_offset_tz = tz_bot.defaults.tzinfo.utcoffset(
chat_bot_tz.emoji_status_expiration_date.replace(tzinfo=None)
)
assert chat_bot.emoji_status_expiration_date.tzinfo == UTC
assert chat_bot_raw.emoji_status_expiration_date.tzinfo == UTC
assert emoji_expire_offset_tz == emoji_expire_offset
def test_to_dict(self, chat):
chat_dict = chat.to_dict()
@ -177,6 +205,7 @@ class TestChatWithoutRequest(TestChatBase):
assert chat_dict["is_forum"] == chat.is_forum
assert chat_dict["active_usernames"] == list(chat.active_usernames)
assert chat_dict["emoji_status_custom_emoji_id"] == chat.emoji_status_custom_emoji_id
assert chat_dict["emoji_status_expiration_date"] == chat.emoji_status_expiration_date
assert (
chat_dict["has_aggressive_anti_spam_enabled"] == chat.has_aggressive_anti_spam_enabled
)
@ -1075,6 +1104,31 @@ class TestChatWithoutRequest(TestChatBase):
monkeypatch.setattr(chat.get_bot(), "unpin_all_forum_topic_messages", make_assertion)
assert await chat.unpin_all_forum_topic_messages(message_thread_id=42)
async def test_unpin_all_general_forum_topic_messages(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id
assert check_shortcut_signature(
Chat.unpin_all_general_forum_topic_messages,
Bot.unpin_all_general_forum_topic_messages,
["chat_id"],
[],
)
assert await check_shortcut_call(
chat.unpin_all_general_forum_topic_messages,
chat.get_bot(),
"unpin_all_general_forum_topic_messages",
shortcut_kwargs=["chat_id"],
)
assert await check_defaults_handling(
chat.unpin_all_general_forum_topic_messages, chat.get_bot()
)
monkeypatch.setattr(
chat.get_bot(), "unpin_all_general_forum_topic_messages", make_assertion
)
assert await chat.unpin_all_general_forum_topic_messages()
async def test_edit_general_forum_topic(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id and kwargs["name"] == "WhatAName"

View file

@ -236,6 +236,7 @@ class TestForumMethodsWithRequest:
assert result is True, "Failed to reopen forum topic"
async def test_unpin_all_forum_topic_messages(self, bot, forum_group_id, real_topic):
# We need 2 or more pinned msgs for this to work, else we get Chat_not_modified error
message_thread_id = real_topic.message_thread_id
pin_msg_tasks = set()
@ -249,10 +250,23 @@ class TestForumMethodsWithRequest:
assert all([await task for task in pin_msg_tasks]) is True, "Message(s) were not pinned"
# We need 2 or more pinned msgs for this to work, else we get Chat_not_modified error
result = await bot.unpin_all_forum_topic_messages(forum_group_id, message_thread_id)
assert result is True, "Failed to unpin all the messages in forum topic"
async def test_unpin_all_general_forum_topic_messages(self, bot, forum_group_id):
# We need 2 or more pinned msgs for this to work, else we get Chat_not_modified error
pin_msg_tasks = set()
awaitables = {bot.send_message(forum_group_id, TEST_MSG_TEXT) for _ in range(2)}
for coro in asyncio.as_completed(awaitables):
msg = await coro
pin_msg_tasks.add(asyncio.create_task(msg.pin()))
assert all([await task for task in pin_msg_tasks]) is True, "Message(s) were not pinned"
result = await bot.unpin_all_general_forum_topic_messages(forum_group_id)
assert result is True, "Failed to unpin all the messages in forum topic"
async def test_edit_general_forum_topic(self, bot, forum_group_id):
result = await bot.edit_general_forum_topic(
chat_id=forum_group_id,

View file

@ -42,6 +42,7 @@ from telegram import (
PollOption,
ProximityAlertTriggered,
Sticker,
Story,
SuccessfulPayment,
Update,
User,
@ -123,6 +124,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()},
{"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)},
@ -227,6 +229,7 @@ def message(bot):
"game",
"photo",
"sticker",
"story",
"video",
"voice",
"video_note",
@ -989,6 +992,7 @@ class TestMessageWithoutRequest(TestMessageBase):
"photo",
"poll",
"sticker",
"story",
"successful_payment",
"video",
"video_note",

View file

@ -19,9 +19,10 @@ from datetime import datetime, timedelta, timezone
import pytest
from telegram import MessageEntity, Poll, PollAnswer, PollOption, User
from telegram import Chat, MessageEntity, Poll, PollAnswer, PollOption, User
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import PollType
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.slots import mro_slots
@ -81,50 +82,58 @@ class TestPollOptionWithoutRequest(TestPollOptionBase):
@pytest.fixture(scope="module")
def poll_answer():
return PollAnswer(
TestPollAnswerBase.poll_id, TestPollAnswerBase.user, TestPollAnswerBase.poll_id
TestPollAnswerBase.poll_id,
TestPollAnswerBase.option_ids,
TestPollAnswerBase.user,
TestPollAnswerBase.voter_chat,
)
class TestPollAnswerBase:
poll_id = "id"
user = User(1, "", False)
option_ids = [2]
user = User(1, "", False)
voter_chat = Chat(1, "")
class TestPollAnswerWithoutRequest(TestPollAnswerBase):
def test_de_json(self):
json_dict = {
"poll_id": self.poll_id,
"user": self.user.to_dict(),
"option_ids": self.option_ids,
"user": self.user.to_dict(),
"voter_chat": self.voter_chat.to_dict(),
}
poll_answer = PollAnswer.de_json(json_dict, None)
assert poll_answer.api_kwargs == {}
assert poll_answer.poll_id == self.poll_id
assert poll_answer.user == self.user
assert poll_answer.option_ids == tuple(self.option_ids)
assert poll_answer.user == self.user
assert poll_answer.voter_chat == self.voter_chat
def test_to_dict(self, poll_answer):
poll_answer_dict = poll_answer.to_dict()
assert isinstance(poll_answer_dict, dict)
assert poll_answer_dict["poll_id"] == poll_answer.poll_id
assert poll_answer_dict["user"] == poll_answer.user.to_dict()
assert poll_answer_dict["option_ids"] == list(poll_answer.option_ids)
assert poll_answer_dict["user"] == poll_answer.user.to_dict()
assert poll_answer_dict["voter_chat"] == poll_answer.voter_chat.to_dict()
def test_equality(self):
a = PollAnswer(123, self.user, [2])
b = PollAnswer(123, User(1, "first", False), [2])
c = PollAnswer(123, self.user, [1, 2])
d = PollAnswer(456, self.user, [2])
e = PollOption("Text", 1)
a = PollAnswer(123, [2], self.user, self.voter_chat)
b = PollAnswer(123, [2], self.user, Chat(1, ""))
c = PollAnswer(123, [2], User(1, "first", False), self.voter_chat)
d = PollAnswer(123, [1, 2], self.user, self.voter_chat)
e = PollAnswer(456, [2], self.user, self.voter_chat)
f = PollOption("Text", 1)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
@ -132,6 +141,22 @@ class TestPollAnswerWithoutRequest(TestPollAnswerBase):
assert a != e
assert hash(a) != hash(e)
assert a != f
assert hash(a) != hash(f)
def test_order_warning(self, recwarn):
expected_warning = (
"From v20.5 the order of `option_ids` and `user` is changed as the latter one"
" became optional. Please update your code to use the new order."
)
PollAnswer(123, [2], self.user, self.voter_chat)
assert len(recwarn) == 0
PollAnswer(123, self.user, [2], self.voter_chat)
assert len(recwarn) == 1
assert str(recwarn[0].message) == expected_warning
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__, "wrong stacklevel"
@pytest.fixture(scope="module")
def poll():

45
tests/test_story.py Normal file
View file

@ -0,0 +1,45 @@
#!/usr/bin/env python
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import Story
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def story():
return Story()
class TestStoryWithoutRequest:
def test_slot_behaviour(self):
story = 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)
assert story.api_kwargs == {}
assert isinstance(story, Story)
def test_to_dict(self):
story = Story()
story_dict = story.to_dict()
assert story_dict == {}

View file

@ -70,7 +70,18 @@ params = [
{"shipping_query": ShippingQuery("id", User(1, "", False), "", None)},
{"pre_checkout_query": PreCheckoutQuery("id", User(1, "", False), "", 0, "")},
{"poll": Poll("id", "?", [PollOption(".", 1)], False, False, False, Poll.REGULAR, True)},
{"poll_answer": PollAnswer("id", User(1, "", False), [1])},
{
"poll_answer": PollAnswer(
"id",
[1],
User(
1,
"",
False,
),
Chat(1, ""),
)
},
{"my_chat_member": chat_member_updated},
{"chat_member": chat_member_updated},
{"chat_join_request": chat_join_request},