Co-authored-by: Mahdyar Hasanpour <mahdyar@duck.com>
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com>
Co-authored-by: Aditya <adityayadav11082@gmail.com>
This commit is contained in:
Harshil 2024-04-12 05:58:25 -04:00 committed by GitHub
parent 3ec7bb819c
commit 5fa457974d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 3264 additions and 162 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-7.1-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.2-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.1** are supported.
All types and methods of the Telegram Bot API **7.2** 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.1-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.2-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@ -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.1** are supported.
All types and methods of the Telegram Bot API **7.2** are supported.
Installing
==========

View file

@ -113,6 +113,10 @@
:align: left
:widths: 1 4
* - :meth:`~telegram.Bot.approve_chat_join_request`
- Used for approving a chat join request
* - :meth:`~telegram.Bot.decline_chat_join_request`
- Used for declining a chat join request
* - :meth:`~telegram.Bot.ban_chat_member`
- Used for banning a member from the chat
* - :meth:`~telegram.Bot.unban_chat_member`
@ -137,10 +141,6 @@
- Used for editing a non-primary invite link
* - :meth:`~telegram.Bot.revoke_chat_invite_link`
- Used for revoking an invite link created by the bot
* - :meth:`~telegram.Bot.approve_chat_join_request`
- Used for approving a chat join request
* - :meth:`~telegram.Bot.decline_chat_join_request`
- Used for declining a chat join request
* - :meth:`~telegram.Bot.set_chat_photo`
- Used for setting a photo to a chat
* - :meth:`~telegram.Bot.delete_chat_photo`
@ -155,6 +155,8 @@
- Used for unpinning a message
* - :meth:`~telegram.Bot.unpin_all_chat_messages`
- Used for unpinning all pinned chat messages
* - :meth:`~telegram.Bot.get_business_connection`
- Used for getting information about the business account.
* - :meth:`~telegram.Bot.get_user_profile_photos`
- Used for obtaining user's profile pictures
* - :meth:`~telegram.Bot.get_chat`
@ -237,6 +239,8 @@
- Used for setting a sticker set of a chat
* - :meth:`~telegram.Bot.delete_chat_sticker_set`
- Used for deleting the set sticker set of a chat
* - :meth:`~telegram.Bot.replace_sticker_in_set`
- Used for replacing a sticker in a set
* - :meth:`~telegram.Bot.set_sticker_position_in_set`
- Used for moving a sticker's position in the set
* - :meth:`~telegram.Bot.set_sticker_set_title`

View file

@ -6,6 +6,7 @@ Available Types
telegram.animation
telegram.audio
telegram.birthdate
telegram.botcommand
telegram.botcommandscope
telegram.botcommandscopeallchatadministrators
@ -18,6 +19,12 @@ Available Types
telegram.botdescription
telegram.botname
telegram.botshortdescription
telegram.businessconnection
telegram.businessintro
telegram.businesslocation
telegram.businessopeninghours
telegram.businessopeninghoursinterval
telegram.businessmessagesdeleted
telegram.callbackquery
telegram.chat
telegram.chatadministratorrights
@ -107,6 +114,7 @@ Available Types
telegram.replykeyboardremove
telegram.replyparameters
telegram.sentwebappmessage
telegram.shareduser
telegram.story
telegram.switchinlinequerychosenchat
telegram.telegramobject

View file

@ -0,0 +1,7 @@
Birthdate
=========
.. autoclass:: telegram.Birthdate
:members:
:show-inheritance:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,8 @@ Handlers
:titlesonly:
telegram.ext.basehandler
telegram.ext.businessconnectionhandler
telegram.ext.businessmessagesdeletedhandler
telegram.ext.callbackqueryhandler
telegram.ext.chatboosthandler
telegram.ext.chatjoinrequesthandler

View file

@ -0,0 +1,7 @@
SharedUser
==========
.. autoclass:: telegram.SharedUser
:members:
:show-inheritance:

View file

@ -79,3 +79,5 @@
.. |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.
.. |business_id_str| replace:: Unique identifier of the business connection on behalf of which the message will be sent.

View file

@ -20,7 +20,7 @@ explicit-preview-rules = true
ignore = ["PLR2004", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PERF203"]
select = ["E", "F", "I", "PL", "UP", "RUF", "PTH", "C4", "B", "PIE", "SIM", "RET", "RSE",
"G", "ISC", "PT", "ASYNC", "TCH", "SLOT", "PERF", "PYI", "FLY", "AIR", "RUF022",
"RUF023", "Q", "INP",]
"RUF023", "Q", "INP", "W"]
# Add "FURB" after it's out of preview
[tool.ruff.lint.per-file-ignores]

View file

@ -22,6 +22,7 @@ __author__ = "devs@python-telegram-bot.org"
__all__ = (
"Animation",
"Audio",
"Birthdate",
"Bot",
"BotCommand",
"BotCommandScope",
@ -35,6 +36,12 @@ __all__ = (
"BotDescription",
"BotName",
"BotShortDescription",
"BusinessConnection",
"BusinessIntro",
"BusinessLocation",
"BusinessMessagesDeleted",
"BusinessOpeningHours",
"BusinessOpeningHoursInterval",
"CallbackGame",
"CallbackQuery",
"Chat",
@ -184,6 +191,7 @@ __all__ = (
"SecureData",
"SecureValue",
"SentWebAppMessage",
"SharedUser",
"ShippingAddress",
"ShippingOption",
"ShippingQuery",
@ -224,6 +232,7 @@ __all__ = (
from . import _version, constants, error, helpers, request, warnings
from ._birthdate import Birthdate
from ._bot import Bot
from ._botcommand import BotCommand
from ._botcommandscope import (
@ -238,6 +247,14 @@ from ._botcommandscope import (
)
from ._botdescription import BotDescription, BotShortDescription
from ._botname import BotName
from ._business import (
BusinessConnection,
BusinessIntro,
BusinessLocation,
BusinessMessagesDeleted,
BusinessOpeningHours,
BusinessOpeningHoursInterval,
)
from ._callbackquery import CallbackQuery
from ._chat import Chat
from ._chatadministratorrights import ChatAdministratorRights
@ -393,7 +410,7 @@ from ._reply import ExternalReplyInfo, ReplyParameters, TextQuote
from ._replykeyboardmarkup import ReplyKeyboardMarkup
from ._replykeyboardremove import ReplyKeyboardRemove
from ._sentwebappmessage import SentWebAppMessage
from ._shared import ChatShared, UsersShared
from ._shared import ChatShared, SharedUser, UsersShared
from ._story import Story
from ._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
from ._telegramobject import TelegramObject

88
telegram/_birthdate.py Normal file
View file

@ -0,0 +1,88 @@
#!/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 an object that represents a Telegram Birthday."""
from datetime import datetime
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class Birthdate(TelegramObject):
"""
This object represents a user's birthday.
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.
.. versionadded:: NEXT.VERSION
Args:
day (:obj:`int`): Day of the user's birth; 1-31.
month (:obj:`int`): Month of the user's birth; 1-12.
year (:obj:`int`, optional): Year of the user's birth.
Attributes:
day (:obj:`int`): Day of the user's birth; 1-31.
month (:obj:`int`): Month of the user's birth; 1-12.
year (:obj:`int`): Optional. Year of the user's birth.
"""
__slots__ = ("day", "month", "year")
def __init__(
self,
day: int,
month: int,
year: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.day: int = day
self.month: int = month
# Optional
self.year: Optional[int] = year
self._id_attrs = (
self.day,
self.month,
)
self._freeze()
def to_date(self, year: Optional[int] = None) -> datetime:
"""Return the birthdate as a datetime object.
Args:
year (:obj:`int`, optional): The year to use. Required, if the :attr:`year` was not
present.
Returns:
:obj:`datetime.datetime`: The birthdate as a datetime object.
"""
if self.year is None and year is None:
raise ValueError(
"The `year` argument is required if the `year` attribute was not present."
)
return datetime(year or self.year, self.month, self.day) # type: ignore[arg-type]

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=no-self-argument, not-callable, no-member, too-many-arguments
# pylint: disable=too-many-arguments
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
@ -57,6 +57,7 @@ from telegram._botcommand import BotCommand
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
@ -667,6 +668,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
caption_entities: Optional[Sequence["MessageEntity"]] = None,
link_preview_options: ODVInput["LinkPreviewOptions"] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -723,6 +725,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
if caption_entities is not None:
data["caption_entities"] = caption_entities
if business_connection_id is not None:
data["business_connection_id"] = business_connection_id
result = await self._post(
endpoint,
data,
@ -911,6 +916,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -956,6 +962,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1009,6 +1018,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
parse_mode=parse_mode,
link_preview_options=link_preview_options,
reply_parameters=reply_parameters,
@ -1259,6 +1269,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1319,6 +1330,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1376,6 +1390,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_audio(
@ -1394,6 +1409,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1463,6 +1479,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:obj:`ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1523,6 +1542,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_document(
@ -1539,6 +1559,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1607,6 +1628,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1663,6 +1687,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_sticker(
@ -1675,6 +1700,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1693,8 +1719,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Sticker`): Sticker to send.
|fileinput| Video stickers can only be sent by a ``file_id``. Animated stickers
can't be sent via an HTTP URL.
|fileinput| Video stickers can only be sent by a ``file_id``. Video and animated
stickers can't be sent via an HTTP URL.
Lastly you can pass an existing :class:`telegram.Sticker` object to send.
@ -1723,6 +1749,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1771,6 +1800,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_video(
@ -1791,6 +1821,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -1868,6 +1899,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -1930,6 +1964,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_video_note(
@ -1944,6 +1979,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2006,6 +2042,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2062,6 +2101,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_animation(
@ -2081,6 +2121,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2152,6 +2193,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2213,6 +2257,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_voice(
@ -2228,6 +2273,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2292,6 +2338,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2349,6 +2398,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_media_group(
@ -2361,6 +2411,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2408,6 +2459,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2500,6 +2554,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"protect_content": protect_content,
"message_thread_id": message_thread_id,
"reply_parameters": reply_parameters,
"business_connection_id": business_connection_id,
}
result = await self._post(
@ -2528,6 +2583,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2579,6 +2635,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2647,6 +2706,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def edit_message_live_location(
@ -2806,6 +2866,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2855,6 +2916,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -2934,6 +2998,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_contact(
@ -2948,6 +3013,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -2987,6 +3053,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -3057,6 +3126,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_game(
@ -3068,6 +3138,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -3097,6 +3168,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -3142,6 +3216,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def send_chat_action(
@ -3149,6 +3224,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
chat_id: Union[str, int],
action: str,
message_thread_id: Optional[int] = None,
business_connection_id: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -3170,6 +3246,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_thread_id (:obj:`int`, optional): |message_thread_id_arg|
.. versionadded:: 20.0
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
@ -3182,6 +3261,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"chat_id": chat_id,
"action": action,
"message_thread_id": message_thread_id,
"business_connection_id": business_connection_id,
}
return await self._post(
"sendChatAction",
@ -6147,9 +6227,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""
Use this method to add a new sticker to a set created by the bot. The format of the added
sticker must match the format of the other stickers in the set. Emoji sticker sets can have
up to :tg-const:`telegram.constants.StickerSetLimit.MAX_EMOJI_STICKERS` stickers. Animated
and video sticker sets can have up to
:tg-const:`telegram.constants.StickerSetLimit.MAX_ANIMATED_STICKERS` stickers. Static
up to :tg-const:`telegram.constants.StickerSetLimit.MAX_EMOJI_STICKERS` stickers. Other
sticker sets can have up to
:tg-const:`telegram.constants.StickerSetLimit.MAX_STATIC_STICKERS` stickers.
@ -6234,7 +6312,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
name: str,
title: str,
stickers: Sequence["InputSticker"],
sticker_format: str,
sticker_format: Optional[str] = None,
sticker_type: Optional[str] = None,
needs_repainting: Optional[bool] = None,
*,
@ -6287,6 +6365,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
.. versionadded:: 20.2
.. deprecated:: NEXT.VERSION
Use :paramref:`telegram.InputSticker.format` instead.
sticker_type (:obj:`str`, optional): Type of stickers in the set, pass
:attr:`telegram.Sticker.REGULAR` or :attr:`telegram.Sticker.MASK`, or
:attr:`telegram.Sticker.CUSTOM_EMOJI`. By default, a regular sticker set is created
@ -6306,6 +6387,14 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Raises:
:class:`telegram.error.TelegramError`
"""
if sticker_format is not None:
warn(
"The parameter `sticker_format` is deprecated. Use the parameter"
" `InputSticker.format` in the parameter `stickers` instead.",
stacklevel=2,
category=PTBDeprecationWarning,
)
data: JSONDict = {
"user_id": user_id,
"name": name,
@ -6399,6 +6488,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
self,
name: str,
user_id: int,
format: str, # pylint: disable=redefined-builtin
thumbnail: Optional[FileInput] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -6412,9 +6502,21 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
.. versionadded:: 20.2
.. versionchanged:: NEXT.VERSION
As per Bot API 7.2, the new argument :paramref:`format` will be required, and thus the
order of the arguments had to be changed.
Args:
name (:obj:`str`): Sticker set name
user_id (:obj:`int`): User identifier of created sticker set owner.
format (:obj:`str`): Format of the added sticker, must be one of
:tg-const:`telegram.constants.StickerFormat.STATIC` for a
``.WEBP`` or ``.PNG`` image, :tg-const:`telegram.constants.StickerFormat.ANIMATED`
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a
WEBM video.
.. versionadded:: NEXT.VERSION
thumbnail (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`, \
optional): A **.WEBP** or **.PNG** image with the thumbnail, must
be up to :tg-const:`telegram.constants.StickerSetLimit.MAX_STATIC_THUMBNAIL_SIZE`
@ -6447,6 +6549,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"name": name,
"user_id": user_id,
"thumbnail": self._parse_file_input(thumbnail) if thumbnail else None,
"format": format,
}
return await self._post(
@ -6728,6 +6831,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -6803,6 +6907,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -6862,6 +6969,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def stop_poll(
@ -6918,6 +7026,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -6961,6 +7070,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
.. versionadded:: 20.8
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -7007,6 +7119,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def get_my_default_administrator_rights(
@ -8665,6 +8778,95 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs=api_kwargs,
)
async def get_business_connection(
self,
business_connection_id: str,
*,
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,
) -> BusinessConnection:
"""
Use this method to get information about the connection of the bot with a business account.
.. versionadded:: NEXT.VERSION
Args:
business_connection_id (:obj:`str`): Unique identifier of the business connection.
Returns:
:class:`telegram.BusinessConnection`: On success, the object containing the business
connection information is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"business_connection_id": business_connection_id}
return BusinessConnection.de_json( # type: ignore[return-value]
await self._post(
"getBusinessConnection",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
),
bot=self,
)
async def replace_sticker_in_set(
self,
user_id: int,
name: str,
old_sticker: str,
sticker: "InputSticker",
*,
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 replace an existing sticker in a sticker set with a new one.
The method is equivalent to calling :meth:`delete_sticker_from_set`,
then :meth:`add_sticker_to_set`, then :meth:`set_sticker_position_in_set`.
.. versionadded:: NEXT.VERSION
Args:
user_id (:obj:`int`): User identifier of the sticker set owner.
name (:obj:`str`): Sticker set name.
old_sticker (:obj:`str`): File identifier of the replaced sticker.
sticker (:obj:`telegram.InputSticker`): An object with information about the added
sticker. If exactly the same sticker had already been added to the set, then the
set remains unchanged.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"user_id": user_id,
"name": name,
"old_sticker": old_sticker,
"sticker": sticker,
}
return await self._post(
"replaceStickerInSet",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@ -8911,3 +9113,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""Alias for :meth:`get_user_chat_boosts`"""
setMessageReaction = set_message_reaction
"""Alias for :meth:`set_message_reaction`"""
getBusinessConnection = get_business_connection
"""Alias for :meth:`get_business_connection`"""
replaceStickerInSet = replace_sticker_in_set
"""Alias for :meth:`replace_sticker_in_set`"""

445
telegram/_business.py Normal file
View file

@ -0,0 +1,445 @@
#!/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 the Telegram Business related classes."""
from datetime import datetime
from typing import TYPE_CHECKING, Optional, Sequence, Tuple
from telegram._chat import Chat
from telegram._files.location import Location
from telegram._files.sticker import Sticker
from telegram._telegramobject import TelegramObject
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.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class BusinessConnection(TelegramObject):
"""
Describes the connection of the bot with a business account.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal if their :attr:`id`, :attr:`user`, :attr:`user_chat_id`, :attr:`date`,
:attr:`can_reply`, and :attr:`is_enabled` are equal.
.. versionadded:: NEXT.VERSION
Args:
id (:obj:`str`): Unique identifier of the business connection.
user (:class:`telegram.User`): Business account user that created the business connection.
user_chat_id (:obj:`int`): Identifier of a private chat with the user who created the
business connection.
date (:obj:`datetime.datetime`): Date the connection was established in Unix time.
can_reply (:obj:`bool`): True, if the bot can act on behalf of the business account in
chats that were active in the last 24 hours.
is_enabled (:obj:`bool`): True, if the connection is active.
Attributes:
id (:obj:`str`): Unique identifier of the business connection.
user (:class:`telegram.User`): Business account user that created the business connection.
user_chat_id (:obj:`int`): Identifier of a private chat with the user who created the
business connection.
date (:obj:`datetime.datetime`): Date the connection was established in Unix time.
can_reply (:obj:`bool`): True, if the bot can act on behalf of the business account in
chats that were active in the last 24 hours.
is_enabled (:obj:`bool`): True, if the connection is active.
"""
__slots__ = (
"can_reply",
"date",
"id",
"is_enabled",
"user",
"user_chat_id",
)
def __init__(
self,
id: str,
user: "User",
user_chat_id: int,
date: datetime,
can_reply: bool,
is_enabled: bool,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.user: User = user
self.user_chat_id: int = user_chat_id
self.date: datetime = date
self.can_reply: bool = can_reply
self.is_enabled: bool = is_enabled
self._id_attrs = (
self.id,
self.user,
self.user_chat_id,
self.date,
self.can_reply,
self.is_enabled,
)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessConnection"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
data["user"] = User.de_json(data.get("user"), bot)
return super().de_json(data=data, bot=bot)
class BusinessMessagesDeleted(TelegramObject):
"""
This object is received when messages are deleted from a connected business account.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal if their :attr:`business_connection_id`, :attr:`message_ids`, and
:attr:`chat` are equal.
.. versionadded:: NEXT.VERSION
Args:
business_connection_id (:obj:`str`): Unique identifier of the business connection.
chat (:class:`telegram.Chat`): Information about a chat in the business account. The bot
may not have access to the chat or the corresponding user.
message_ids (Sequence[:obj:`int`]): A list of identifiers of the deleted messages in the
chat of the business account.
Attributes:
business_connection_id (:obj:`str`): Unique identifier of the business connection.
chat (:class:`telegram.Chat`): Information about a chat in the business account. The bot
may not have access to the chat or the corresponding user.
message_ids (Tuple[:obj:`int`]): A list of identifiers of the deleted messages in the
chat of the business account.
"""
__slots__ = (
"business_connection_id",
"chat",
"message_ids",
)
def __init__(
self,
business_connection_id: str,
chat: Chat,
message_ids: Sequence[int],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.business_connection_id: str = business_connection_id
self.chat: Chat = chat
self.message_ids: Tuple[int, ...] = parse_sequence_arg(message_ids)
self._id_attrs = (
self.business_connection_id,
self.chat,
self.message_ids,
)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessMessagesDeleted"]:
"""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)
class BusinessIntro(TelegramObject):
"""
This object represents the intro of a business account.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`title`, :attr:`message` and :attr:`sticker` are equal.
.. versionadded:: NEXT.VERSION
Args:
title (:obj:`str`, optional): Title text of the business intro.
message (:obj:`str`, optional): Message text of the business intro.
sticker (:class:`telegram.Sticker`, optional): Sticker of the business intro.
Attributes:
title (:obj:`str`): Optional. Title text of the business intro.
message (:obj:`str`): Optional. Message text of the business intro.
sticker (:class:`telegram.Sticker`): Optional. Sticker of the business intro.
"""
__slots__ = (
"message",
"sticker",
"title",
)
def __init__(
self,
title: Optional[str] = None,
message: Optional[str] = None,
sticker: Optional[Sticker] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.title: Optional[str] = title
self.message: Optional[str] = message
self.sticker: Optional[Sticker] = sticker
self._id_attrs = (self.title, self.message, self.sticker)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessIntro"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
return super().de_json(data=data, bot=bot)
class BusinessLocation(TelegramObject):
"""
This object represents the location of a business account.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`address` is equal.
.. versionadded:: NEXT.VERSION
Args:
address (:obj:`str`): Address of the business.
location (:class:`telegram.Location`, optional): Location of the business.
Attributes:
address (:obj:`str`): Address of the business.
location (:class:`telegram.Location`): Optional. Location of the business.
"""
__slots__ = (
"address",
"location",
)
def __init__(
self,
address: str,
location: Optional[Location] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.address: str = address
self.location: Optional[Location] = location
self._id_attrs = (self.address,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessLocation"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["location"] = Location.de_json(data.get("location"), bot)
return super().de_json(data=data, bot=bot)
class BusinessOpeningHoursInterval(TelegramObject):
"""
This object represents the time intervals describing business opening hours.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`opening_minute` and :attr:`closing_minute` are equal.
.. versionadded:: NEXT.VERSION
Examples:
A day has (24 * 60 =) 1440 minutes, a week has (7 * 1440 =) 10080 minutes.
Starting the the minute's sequence from Monday, example values of
:attr:`opening_minute`, :attr:`closing_minute` will map to the following day times:
* Monday - 8am to 8:30pm:
- ``opening_minute = 480`` :guilabel:`8 * 60`
- ``closing_minute = 1230`` :guilabel:`20 * 60 + 30`
* Tuesday - 24 hours:
- ``opening_minute = 1440`` :guilabel:`24 * 60`
- ``closing_minute = 2879`` :guilabel:`2 * 24 * 60 - 1`
* Sunday - 12am - 11:58pm:
- ``opening_minute = 8640`` :guilabel:`6 * 24 * 60`
- ``closing_minute = 10078`` :guilabel:`7 * 24 * 60 - 2`
Args:
opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday,
marking the start of the time interval during which the business is open;
0 - 7 * 24 * 60.
closing_minute (:obj:`int`): The minute's
sequence number in a week, starting on Monday, marking the end of the time interval
during which the business is open; 0 - 8 * 24 * 60
Attributes:
opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday,
marking the start of the time interval during which the business is open;
0 - 7 * 24 * 60.
closing_minute (:obj:`int`): The minute's
sequence number in a week, starting on Monday, marking the end of the time interval
during which the business is open; 0 - 8 * 24 * 60
"""
__slots__ = ("_closing_time", "_opening_time", "closing_minute", "opening_minute")
def __init__(
self,
opening_minute: int,
closing_minute: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.opening_minute: int = opening_minute
self.closing_minute: int = closing_minute
self._opening_time: Optional[Tuple[int, int, int]] = None
self._closing_time: Optional[Tuple[int, int, int]] = None
self._id_attrs = (self.opening_minute, self.closing_minute)
self._freeze()
def _parse_minute(self, minute: int) -> Tuple[int, int, int]:
return (minute // 1440, minute % 1440 // 60, minute % 1440 % 60)
@property
def opening_time(self) -> Tuple[int, int, int]:
"""Convenience attribute. A :obj:`tuple` parsed from :attr:`opening_minute`. It contains
the `weekday`, `hour` and `minute` in the same ranges as :attr:`datetime.datetime.weekday`,
:attr:`datetime.datetime.hour` and :attr:`datetime.datetime.minute`
Returns:
Tuple[:obj:`int`, :obj:`int`, :obj:`int`]:
"""
if self._opening_time is None:
self._opening_time = self._parse_minute(self.opening_minute)
return self._opening_time
@property
def closing_time(self) -> Tuple[int, int, int]:
"""Convenience attribute. A :obj:`tuple` parsed from :attr:`closing_minute`. It contains
the `weekday`, `hour` and `minute` in the same ranges as :attr:`datetime.datetime.weekday`,
:attr:`datetime.datetime.hour` and :attr:`datetime.datetime.minute`
Returns:
Tuple[:obj:`int`, :obj:`int`, :obj:`int`]:
"""
if self._closing_time is None:
self._closing_time = self._parse_minute(self.closing_minute)
return self._closing_time
class BusinessOpeningHours(TelegramObject):
"""
This object represents the opening hours of a business account.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`time_zone_name` and :attr:`opening_hours` are equal.
.. versionadded:: NEXT.VERSION
Args:
time_zone_name (:obj:`str`): Unique name of the time zone for which the opening
hours are defined.
opening_hours (Sequence[:class:`telegram.BusinessOpeningHoursInterval`]): List of
time intervals describing business opening hours.
Attributes:
time_zone_name (:obj:`str`): Unique name of the time zone for which the opening
hours are defined.
opening_hours (Sequence[:class:`telegram.BusinessOpeningHoursInterval`]): List of
time intervals describing business opening hours.
"""
__slots__ = ("opening_hours", "time_zone_name")
def __init__(
self,
time_zone_name: str,
opening_hours: Sequence[BusinessOpeningHoursInterval],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.time_zone_name: str = time_zone_name
self.opening_hours: Sequence[BusinessOpeningHoursInterval] = parse_sequence_arg(
opening_hours
)
self._id_attrs = (self.time_zone_name, self.opening_hours)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessOpeningHours"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["opening_hours"] = BusinessOpeningHoursInterval.de_list(
data.get("opening_hours"), bot
)
return super().de_json(data=data, bot=bot)

View file

@ -23,6 +23,7 @@ from html import escape
from typing import TYPE_CHECKING, Final, Optional, Sequence, Tuple, Union
from telegram import constants
from telegram._birthdate import Birthdate
from telegram._chatlocation import ChatLocation
from telegram._chatpermissions import ChatPermissions
from telegram._files.chatphoto import ChatPhoto
@ -44,6 +45,9 @@ if TYPE_CHECKING:
Animation,
Audio,
Bot,
BusinessIntro,
BusinessLocation,
BusinessOpeningHours,
ChatInviteLink,
ChatMember,
Contact,
@ -169,6 +173,21 @@ class Chat(TelegramObject):
only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
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:: NEXT.VERSION
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:: NEXT.VERSION
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:: NEXT.VERSION
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
@ -229,6 +248,14 @@ class Chat(TelegramObject):
and bots in the group. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
birthdate (:obj:`telegram.Birthdate`, optional): For private chats,
the date of birth of the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: NEXT.VERSION
personal_chat (:obj:`telegram.Chat`, optional): For private chats, the personal channel of
the user. 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
@ -312,6 +339,21 @@ class Chat(TelegramObject):
obtained via :meth:`~telegram.Bot.get_chat`.
.. versionadded:: 20.0
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:: NEXT.VERSION
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:: NEXT.VERSION
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:: NEXT.VERSION
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
@ -372,6 +414,14 @@ class Chat(TelegramObject):
and bots in the group. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 21.0
birthdate (:obj:`telegram.Birthdate`): Optional. For private chats,
the date of birth of the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: NEXT.VERSION
personal_chat (:obj:`telegram.Chat`): Optional. For private chats, the personal channel of
the user. 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
@ -383,6 +433,10 @@ class Chat(TelegramObject):
"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",
@ -405,6 +459,7 @@ class Chat(TelegramObject):
"location",
"message_auto_delete_time",
"permissions",
"personal_chat",
"photo",
"pinned_message",
"profile_accent_color_id",
@ -470,6 +525,11 @@ class Chat(TelegramObject):
has_visible_history: Optional[bool] = None,
unrestrict_boost_count: Optional[int] = None,
custom_emoji_sticker_set_name: Optional[str] = None,
birthdate: Optional[Birthdate] = None,
personal_chat: Optional["Chat"] = None,
business_intro: Optional["BusinessIntro"] = None,
business_location: Optional["BusinessLocation"] = None,
business_opening_hours: Optional["BusinessOpeningHours"] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -519,6 +579,11 @@ class Chat(TelegramObject):
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.birthdate: Optional[Birthdate] = birthdate
self.personal_chat: Optional["Chat"] = personal_chat
self.business_intro: Optional["BusinessIntro"] = business_intro
self.business_location: Optional["BusinessLocation"] = business_location
self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours
self._id_attrs = (self.id,)
@ -581,12 +646,24 @@ class Chat(TelegramObject):
)
data["photo"] = ChatPhoto.de_json(data.get("photo"), bot)
from telegram import Message # pylint: disable=import-outside-toplevel
from telegram import ( # pylint: disable=import-outside-toplevel
BusinessIntro,
BusinessLocation,
BusinessOpeningHours,
Message,
)
data["pinned_message"] = Message.de_json(data.get("pinned_message"), bot)
data["permissions"] = ChatPermissions.de_json(data.get("permissions"), bot)
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["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(
data.get("business_opening_hours"), bot
)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
@ -1444,6 +1521,7 @@ class Chat(TelegramObject):
message_thread_id: Optional[int] = None,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1483,6 +1561,7 @@ class Chat(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def delete_message(
@ -1558,6 +1637,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1598,12 +1678,14 @@ class Chat(TelegramObject):
parse_mode=parse_mode,
caption_entities=caption_entities,
reply_parameters=reply_parameters,
business_connection_id=business_connection_id,
)
async def send_chat_action(
self,
action: str,
message_thread_id: Optional[int] = None,
business_connection_id: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1630,6 +1712,7 @@ class Chat(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
send_action = send_chat_action
@ -1647,6 +1730,7 @@ class Chat(TelegramObject):
message_thread_id: Optional[int] = None,
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1687,6 +1771,7 @@ class Chat(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
business_connection_id=business_connection_id,
)
async def send_contact(
@ -1700,6 +1785,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1739,6 +1825,7 @@ class Chat(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_audio(
@ -1756,6 +1843,7 @@ class Chat(TelegramObject):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1799,6 +1887,7 @@ class Chat(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
)
async def send_document(
@ -1814,6 +1903,7 @@ class Chat(TelegramObject):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1855,6 +1945,7 @@ class Chat(TelegramObject):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_dice(
@ -1865,6 +1956,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1899,6 +1991,7 @@ class Chat(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_game(
@ -1909,6 +2002,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1943,6 +2037,7 @@ class Chat(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_invoice(
@ -2052,6 +2147,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2093,6 +2189,7 @@ class Chat(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_animation(
@ -2111,6 +2208,7 @@ class Chat(TelegramObject):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2155,6 +2253,7 @@ class Chat(TelegramObject):
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
)
async def send_sticker(
@ -2166,6 +2265,7 @@ class Chat(TelegramObject):
message_thread_id: Optional[int] = None,
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2201,6 +2301,7 @@ class Chat(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
business_connection_id=business_connection_id,
)
async def send_venue(
@ -2218,6 +2319,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2261,6 +2363,7 @@ class Chat(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_video(
@ -2280,6 +2383,7 @@ class Chat(TelegramObject):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2325,6 +2429,7 @@ class Chat(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
business_connection_id=business_connection_id,
)
async def send_video_note(
@ -2338,6 +2443,7 @@ class Chat(TelegramObject):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2377,6 +2483,7 @@ class Chat(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_voice(
@ -2391,6 +2498,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2431,6 +2539,7 @@ class Chat(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_poll(
@ -2452,6 +2561,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2497,6 +2607,7 @@ class Chat(TelegramObject):
explanation_entities=explanation_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_copy(

View file

@ -36,6 +36,10 @@ class InputSticker(TelegramObject):
.. versionadded:: 20.2
.. versionchanged:: NEXT.VERSION
As of Bot API 7.2, the new argument :paramref:`format` is a required argument, and thus the
order of the arguments has changed.
Args:
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`): The
added sticker. |uploadinputnopath| Animated and video stickers can't be uploaded via
@ -52,6 +56,13 @@ class InputSticker(TelegramObject):
:tg-const:`telegram.constants.StickerLimit.MAX_KEYWORD_LENGTH` characters. For
":tg-const:`telegram.constants.StickerType.REGULAR`" and
":tg-const:`telegram.constants.StickerType.CUSTOM_EMOJI`" stickers only.
format (:obj:`str`): Format of the added sticker, must be one of
:tg-const:`telegram.constants.StickerFormat.STATIC` for a
``.WEBP`` or ``.PNG`` image, :tg-const:`telegram.constants.StickerFormat.ANIMATED`
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a WEBM
video.
.. versionadded:: NEXT.VERSION
Attributes:
sticker (:obj:`str` | :class:`telegram.InputFile`): The added sticker.
@ -67,15 +78,23 @@ class InputSticker(TelegramObject):
:tg-const:`telegram.constants.StickerLimit.MAX_KEYWORD_LENGTH` characters. For
":tg-const:`telegram.constants.StickerType.REGULAR`" and
":tg-const:`telegram.constants.StickerType.CUSTOM_EMOJI`" stickers only.
":tg-const:`telegram.constants.StickerType.CUSTOM_EMOJI`" stickers only.
format (:obj:`str`): Format of the added sticker, must be one of
:tg-const:`telegram.constants.StickerFormat.STATIC` for a
``.WEBP`` or ``.PNG`` image, :tg-const:`telegram.constants.StickerFormat.ANIMATED`
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a WEBM
video.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("emoji_list", "keywords", "mask_position", "sticker")
__slots__ = ("emoji_list", "format", "keywords", "mask_position", "sticker")
def __init__(
self,
sticker: FileInput,
emoji_list: Sequence[str],
format: str, # pylint: disable=redefined-builtin
mask_position: Optional[MaskPosition] = None,
keywords: Optional[Sequence[str]] = None,
*,
@ -91,6 +110,7 @@ class InputSticker(TelegramObject):
attach=True,
)
self.emoji_list: Tuple[str, ...] = parse_sequence_arg(emoji_list)
self.format: str = format
self.mask_position: Optional[MaskPosition] = mask_position
self.keywords: Tuple[str, ...] = parse_sequence_arg(keywords)

View file

@ -27,6 +27,8 @@ from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import Bot
@ -227,6 +229,11 @@ class StickerSet(TelegramObject):
.. versionchanged:: 20.0
The parameter ``contains_masks`` has been removed. Use :paramref:`sticker_type` instead.
.. versionchanged:: NEXT.VERSION
The parameters ``is_video`` and ``is_animated`` are deprecated and now made optional. Thus,
the order of the arguments had to be changed.
.. versionchanged:: 20.5
|removed_thumb_note|
@ -234,9 +241,16 @@ class StickerSet(TelegramObject):
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
is_animated (:obj:`bool`): :obj:`True`, if the sticker set contains animated stickers.
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. deprecated:: NEXT.VERSION
Bot API 7.2 deprecated this field. This parameter will be removed in a future
version of the library.
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. versionadded:: 13.11
.. deprecated:: NEXT.VERSION
Bot API 7.2 deprecated this field. This parameter will be removed in a future
version of the library.
stickers (Sequence[:class:`telegram.Sticker`]): List of all set stickers.
.. versionchanged:: 20.0
@ -256,9 +270,16 @@ class StickerSet(TelegramObject):
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
is_animated (:obj:`bool`): :obj:`True`, if the sticker set contains animated stickers.
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. deprecated:: NEXT.VERSION
Bot API 7.2 deprecated this field. This parameter will be removed in a future
version of the library.
is_video (:obj:`bool`): :obj:`True`, if the sticker set contains video stickers.
.. versionadded:: 13.11
.. deprecated:: NEXT.VERSION
Bot API 7.2 deprecated this field. This parameter will be removed in a future
version of the library.
stickers (Tuple[:class:`telegram.Sticker`]): List of all set stickers.
.. versionchanged:: 20.0
@ -289,10 +310,10 @@ class StickerSet(TelegramObject):
self,
name: str,
title: str,
is_animated: bool,
stickers: Sequence[Sticker],
is_video: bool,
sticker_type: str,
is_animated: Optional[bool] = None,
is_video: Optional[bool] = None,
thumbnail: Optional[PhotoSize] = None,
*,
api_kwargs: Optional[JSONDict] = None,
@ -300,13 +321,19 @@ class StickerSet(TelegramObject):
super().__init__(api_kwargs=api_kwargs)
self.name: str = name
self.title: str = title
self.is_animated: bool = is_animated
self.is_video: bool = is_video
self.stickers: Tuple[Sticker, ...] = parse_sequence_arg(stickers)
self.sticker_type: str = sticker_type
# Optional
self.thumbnail: Optional[PhotoSize] = thumbnail
if is_animated is not None or is_video is not None:
warn(
"The parameters `is_animated` and `is_video` are deprecated and will be removed "
"in a future version.",
PTBDeprecationWarning,
stacklevel=2,
)
self.is_animated: Optional[bool] = is_animated
self.is_video: Optional[bool] = is_video
self._id_attrs = (self.name,)
self._freeze()

View file

@ -17,6 +17,7 @@
# 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 two objects to request chats/users."""
from typing import TYPE_CHECKING, Optional
from telegram._chatadministratorrights import ChatAdministratorRights
@ -56,6 +57,16 @@ class KeyboardButtonRequestUsers(TelegramObject):
.
.. versionadded:: 20.8
request_name (:obj:`bool`, optional): Pass :obj:`True` to request the users' first and last
name.
.. versionadded:: NEXT.VERSION
request_username (:obj:`bool`, optional): Pass :obj:`True` to request the users' username.
.. versionadded:: NEXT.VERSION
request_photo (:obj:`bool`, optional): Pass :obj:`True` to request the users' photo.
.. versionadded:: NEXT.VERSION
Attributes:
request_id (:obj:`int`): Identifier of the request.
@ -71,11 +82,25 @@ class KeyboardButtonRequestUsers(TelegramObject):
.
.. versionadded:: 20.8
request_name (:obj:`bool`): Optional. Pass :obj:`True` to request the users' first and last
name.
.. versionadded:: NEXT.VERSION
request_username (:obj:`bool`): Optional. Pass :obj:`True` to request the users' username.
.. versionadded:: NEXT.VERSION
request_photo (:obj:`bool`): Optional. Pass :obj:`True` to request the users' photo.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
"max_quantity",
"request_id",
"request_name",
"request_photo",
"request_username",
"user_is_bot",
"user_is_premium",
)
@ -86,6 +111,9 @@ class KeyboardButtonRequestUsers(TelegramObject):
user_is_bot: Optional[bool] = None,
user_is_premium: Optional[bool] = None,
max_quantity: Optional[int] = None,
request_name: Optional[bool] = None,
request_username: Optional[bool] = None,
request_photo: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -97,6 +125,9 @@ class KeyboardButtonRequestUsers(TelegramObject):
self.user_is_bot: Optional[bool] = user_is_bot
self.user_is_premium: Optional[bool] = user_is_premium
self.max_quantity: Optional[int] = max_quantity
self.request_name: Optional[bool] = request_name
self.request_username: Optional[bool] = request_username
self.request_photo: Optional[bool] = request_photo
self._id_attrs = (self.request_id,)
@ -138,6 +169,15 @@ class KeyboardButtonRequestChat(TelegramObject):
applied.
bot_is_member (:obj:`bool`, optional): Pass :obj:`True` to request a chat with the bot
as a member. Otherwise, no additional restrictions are applied.
request_title (:obj:`bool`, optional): Pass :obj:`True` to request the chat's title.
.. versionadded:: NEXT.VERSION
request_username (:obj:`bool`, optional): Pass :obj:`True` to request the chat's username.
.. versionadded:: NEXT.VERSION
request_photo (:obj:`bool`, optional): Pass :obj:`True` to request the chat's photo.
.. versionadded:: NEXT.VERSION
Attributes:
request_id (:obj:`int`): Identifier of the request.
chat_is_channel (:obj:`bool`): Pass :obj:`True` to request a channel chat, pass
@ -145,7 +185,7 @@ class KeyboardButtonRequestChat(TelegramObject):
chat_is_forum (:obj:`bool`): Optional. Pass :obj:`True` to request a forum supergroup, pass
:obj:`False` to request a non-forum chat. If not specified, no additional
restrictions are applied.
chat_has_username (:obj:`bool`, optional): Pass :obj:`True` to request a supergroup or a
chat_has_username (:obj:`bool`): Optional. Pass :obj:`True` to request a supergroup or a
channel with a username, pass :obj:`False` to request a chat without a username. If
not specified, no additional restrictions are applied.
chat_is_created (:obj:`bool`) Optional. Pass :obj:`True` to request a chat owned by the
@ -159,6 +199,15 @@ class KeyboardButtonRequestChat(TelegramObject):
applied.
bot_is_member (:obj:`bool`) Optional. Pass :obj:`True` to request a chat with the bot
as a member. Otherwise, no additional restrictions are applied.
request_title (:obj:`bool`): Optional. Pass :obj:`True` to request the chat's title.
.. versionadded:: NEXT.VERSION
request_username (:obj:`bool`): Optional. Pass :obj:`True` to request the chat's username.
.. versionadded:: NEXT.VERSION
request_photo (:obj:`bool`): Optional. Pass :obj:`True` to request the chat's photo.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
@ -169,6 +218,9 @@ class KeyboardButtonRequestChat(TelegramObject):
"chat_is_created",
"chat_is_forum",
"request_id",
"request_photo",
"request_title",
"request_username",
"user_administrator_rights",
)
@ -182,6 +234,9 @@ class KeyboardButtonRequestChat(TelegramObject):
user_administrator_rights: Optional[ChatAdministratorRights] = None,
bot_administrator_rights: Optional[ChatAdministratorRights] = None,
bot_is_member: Optional[bool] = None,
request_title: Optional[bool] = None,
request_username: Optional[bool] = None,
request_photo: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -199,6 +254,9 @@ class KeyboardButtonRequestChat(TelegramObject):
)
self.bot_administrator_rights: Optional[ChatAdministratorRights] = bot_administrator_rights
self.bot_is_member: Optional[bool] = bot_is_member
self.request_title: Optional[bool] = request_title
self.request_username: Optional[bool] = request_username
self.request_photo: Optional[bool] = request_photo
self._id_attrs = (self.request_id,)

View file

@ -18,6 +18,7 @@
# 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 Message."""
import datetime
import re
from html import escape
@ -301,6 +302,11 @@ class Message(MaybeInaccessibleMessage):
forwarded.
.. versionadded:: 13.9
is_from_offline (:obj:`bool`, optional): :obj:`True`, if the message was sent
by an implicit action, for example, as an away or a greeting business message,
or as a scheduled message.
.. versionadded:: NEXT.VERSION
media_group_id (:obj:`str`, optional): The unique identifier of a media message group this
message belongs to.
text (:obj:`str`, optional): For text messages, the actual UTF-8 text of the message,
@ -534,6 +540,18 @@ class Message(MaybeInaccessibleMessage):
message boosted the chat, the number of boosts added by the user.
.. versionadded:: 21.0
business_connection_id (:obj:`str`, optional): Unique identifier of the business connection
from which the message was received. If non-empty, the message belongs to a chat of the
corresponding business account that is independent from any potential bot chat which
might share the same identifier.
.. versionadded:: NEXT.VERSION
sender_business_bot (:obj:`telegram.User`, optional): The bot that actually sent the
message on behalf of the business account. Available only for outgoing messages sent
on behalf of the connected business account.
.. versionadded:: NEXT.VERSION
Attributes:
message_id (:obj:`int`): Unique message identifier inside this chat.
@ -568,6 +586,11 @@ class Message(MaybeInaccessibleMessage):
forwarded.
.. versionadded:: 13.9
is_from_offline (:obj:`bool`): Optional. :obj:`True`, if the message was sent
by an implicit action, for example, as an away or a greeting business message,
or as a scheduled message.
.. versionadded:: NEXT.VERSION
media_group_id (:obj:`str`): Optional. The unique identifier of a media message group this
message belongs to.
text (:obj:`str`): Optional. For text messages, the actual UTF-8 text of the message,
@ -817,6 +840,19 @@ class Message(MaybeInaccessibleMessage):
.. versionadded:: 21.0
business_connection_id (:obj:`str`): Optional. Unique identifier of the business connection
from which the message was received. If non-empty, the message belongs to a chat of the
corresponding business account that is independent from any potential bot chat which
might share the same identifier.
.. versionadded:: NEXT.VERSION
sender_business_bot (:obj:`telegram.User`): Optional. The bot that actually sent the
message on behalf of the business account. Available only for outgoing messages sent
on behalf of the connected business account.
.. 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.
@ -836,6 +872,7 @@ class Message(MaybeInaccessibleMessage):
"audio",
"author_signature",
"boost_added",
"business_connection_id",
"caption",
"caption_entities",
"channel_chat_created",
@ -866,6 +903,7 @@ class Message(MaybeInaccessibleMessage):
"has_protected_content",
"invoice",
"is_automatic_forward",
"is_from_offline",
"is_topic_message",
"left_chat_member",
"link_preview_options",
@ -888,6 +926,7 @@ class Message(MaybeInaccessibleMessage):
"reply_to_message",
"reply_to_story",
"sender_boost_count",
"sender_business_bot",
"sender_chat",
"sticker",
"story",
@ -987,6 +1026,9 @@ class Message(MaybeInaccessibleMessage):
reply_to_story: Optional[Story] = None,
boost_added: Optional[ChatBoostAdded] = None,
sender_boost_count: Optional[int] = None,
business_connection_id: Optional[str] = None,
sender_business_bot: Optional[User] = None,
is_from_offline: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -1082,6 +1124,9 @@ class Message(MaybeInaccessibleMessage):
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.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._effective_attachment = DEFAULT_NONE
@ -1224,6 +1269,7 @@ class Message(MaybeInaccessibleMessage):
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)
data["sender_business_bot"] = User.de_json(data.get("sender_business_bot"), bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
@ -1575,6 +1621,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_message(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -1620,6 +1667,7 @@ class Message(MaybeInaccessibleMessage):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=self.business_connection_id,
)
async def reply_markdown(
@ -1650,6 +1698,7 @@ class Message(MaybeInaccessibleMessage):
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
parse_mode=ParseMode.MARKDOWN,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -1700,6 +1749,7 @@ class Message(MaybeInaccessibleMessage):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=self.business_connection_id,
)
async def reply_markdown_v2(
@ -1730,6 +1780,7 @@ class Message(MaybeInaccessibleMessage):
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
parse_mode=ParseMode.MARKDOWN_V2,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -1776,6 +1827,7 @@ class Message(MaybeInaccessibleMessage):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=self.business_connection_id,
)
async def reply_html(
@ -1806,6 +1858,7 @@ class Message(MaybeInaccessibleMessage):
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
parse_mode=ParseMode.HTML,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -1852,6 +1905,7 @@ class Message(MaybeInaccessibleMessage):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=self.business_connection_id,
)
async def reply_media_group(
@ -1882,6 +1936,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_media_group(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -1927,6 +1982,7 @@ class Message(MaybeInaccessibleMessage):
caption=caption,
parse_mode=parse_mode,
caption_entities=caption_entities,
business_connection_id=self.business_connection_id,
)
async def reply_photo(
@ -1958,6 +2014,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_photo(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2004,6 +2061,7 @@ class Message(MaybeInaccessibleMessage):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
business_connection_id=self.business_connection_id,
)
async def reply_audio(
@ -2038,6 +2096,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_audio(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2087,6 +2146,7 @@ class Message(MaybeInaccessibleMessage):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
)
async def reply_document(
@ -2119,6 +2179,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_document(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2166,6 +2227,7 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
)
async def reply_animation(
@ -2201,6 +2263,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_animation(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2251,6 +2314,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
)
async def reply_sticker(
@ -2278,6 +2342,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_sticker(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2320,6 +2385,7 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
business_connection_id=self.business_connection_id,
)
async def reply_video(
@ -2356,6 +2422,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_video(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2407,6 +2474,7 @@ class Message(MaybeInaccessibleMessage):
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
)
async def reply_video_note(
@ -2437,6 +2505,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_video_note(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2482,6 +2551,7 @@ class Message(MaybeInaccessibleMessage):
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
business_connection_id=self.business_connection_id,
)
async def reply_voice(
@ -2513,6 +2583,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_voice(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2559,6 +2630,7 @@ class Message(MaybeInaccessibleMessage):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
)
async def reply_location(
@ -2591,6 +2663,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_location(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2638,6 +2711,7 @@ class Message(MaybeInaccessibleMessage):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
)
async def reply_venue(
@ -2672,6 +2746,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_venue(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2721,6 +2796,7 @@ class Message(MaybeInaccessibleMessage):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
)
async def reply_contact(
@ -2751,6 +2827,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_contact(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2796,6 +2873,7 @@ class Message(MaybeInaccessibleMessage):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
)
async def reply_poll(
@ -2833,6 +2911,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_poll(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2885,6 +2964,7 @@ class Message(MaybeInaccessibleMessage):
explanation_entities=explanation_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
)
async def reply_dice(
@ -2911,6 +2991,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_dice(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2952,6 +3033,7 @@ class Message(MaybeInaccessibleMessage):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
)
async def reply_chat_action(
@ -2970,6 +3052,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_chat_action(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -2994,6 +3077,7 @@ class Message(MaybeInaccessibleMessage):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=self.business_connection_id,
)
async def reply_game(
@ -3020,6 +3104,7 @@ class Message(MaybeInaccessibleMessage):
await bot.send_game(
update.effective_message.chat_id,
message_thread_id=update.effective_message.message_thread_id,
business_connection_id=self.business_connection_id,
*args,
**kwargs,
)
@ -3063,6 +3148,7 @@ class Message(MaybeInaccessibleMessage):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=self.business_connection_id,
)
async def reply_invoice(

View file

@ -17,10 +17,21 @@
# 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 two objects used for request chats/users service messages."""
from typing import Optional, Sequence, Tuple
from typing import TYPE_CHECKING, Optional, Sequence, Tuple
from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram._utils.warnings_transition import (
build_deprecation_warning_message,
warn_about_deprecated_attr_in_property,
)
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram._bot import Bot
class UsersShared(TelegramObject):
@ -29,48 +40,118 @@ class UsersShared(TelegramObject):
using a :class:`telegram.KeyboardButtonRequestUsers` button.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_id` and :attr:`user_ids` are equal.
considered equal, if their :attr:`request_id` and :attr:`users` are equal.
.. versionadded:: 20.8
Bot API 7.0 replaces ``UserShared`` with this class. The only difference is that now
the :attr:`user_ids` is a sequence instead of a single integer.
.. versionchanged:: NEXT.VERSION
The argument :attr:`users` is now considered for the equality comparison instead of
:attr:`user_ids`.
Args:
request_id (:obj:`int`): Identifier of the request.
user_ids (Sequence[:obj:`int`]): Identifiers of the shared users. These numbers may have
more than 32 significant bits and some programming languages may have difficulty/silent
defects in interpreting them. But they have at most 52 significant bits, so 64-bit
integers or double-precision float types are safe for storing these identifiers. The
bot may not have access to the users and could be unable to use these identifiers,
unless the users are already known to the bot by some other means.
users (Sequence[:class:`telegram.SharedUser`]): Information about users shared with the
bot.
.. versionadded:: NEXT.VERSION
.. deprecated:: NEXT.VERSION
In future versions, this argument will become keyword only.
user_ids (Sequence[:obj:`int`], optional): Identifiers of the shared users. These numbers
may have more than 32 significant bits and some programming languages may have
difficulty/silent defects in interpreting them. But they have at most 52 significant
bits, so 64-bit integers or double-precision float types are safe for storing these
identifiers. The bot may not have access to the users and could be unable to use
these identifiers, unless the users are already known to the bot by some other means.
.. deprecated:: NEXT.VERSION
Bot API 7.2 introduced by :paramref:`users`, replacing this argument. Hence, this
argument is now optional and will be removed in future versions.
Attributes:
request_id (:obj:`int`): Identifier of the request.
user_ids (Tuple[:obj:`int`]): Identifiers of the shared users. These numbers may have
more than 32 significant bits and some programming languages may have difficulty/silent
defects in interpreting them. But they have at most 52 significant bits, so 64-bit
integers or double-precision float types are safe for storing these identifiers. The
bot may not have access to the users and could be unable to use these identifiers,
unless the users are already known to the bot by some other means.
users (Tuple[:class:`telegram.SharedUser`]): Information about users shared with the
bot.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("request_id", "user_ids")
__slots__ = ("request_id", "users")
def __init__(
self,
request_id: int,
user_ids: Sequence[int],
user_ids: Optional[Sequence[int]] = None,
users: Optional[Sequence["SharedUser"]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.request_id: int = request_id
self.user_ids: Tuple[int, ...] = tuple(user_ids)
self._id_attrs = (self.request_id, self.user_ids)
if users is None:
raise TypeError("`users` is a required argument since Bot API 7.2")
self.users: Tuple[SharedUser, ...] = parse_sequence_arg(users)
if user_ids is not None:
warn(
build_deprecation_warning_message(
deprecated_name="user_ids",
new_name="users",
object_type="parameter",
bot_api_version="7.2",
),
PTBDeprecationWarning,
stacklevel=2,
)
self._id_attrs = (self.request_id, self.users)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["UsersShared"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["users"] = SharedUser.de_list(data.get("users"), bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
if user_ids := data.get("user_ids"):
api_kwargs = {"user_ids": user_ids}
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
@property
def user_ids(self) -> Tuple[int, ...]:
"""
Tuple[:obj:`int`]: Identifiers of the shared users. These numbers may have
more than 32 significant bits and some programming languages may have difficulty/silent
defects in interpreting them. But they have at most 52 significant bits, so 64-bit
integers or double-precision float types are safe for storing these identifiers. The
bot may not have access to the users and could be unable to use these identifiers,
unless the users are already known to the bot by some other means.
.. deprecated:: NEXT.VERSION
As Bot API 7.2 replaces this attribute with :attr:`users`, this attribute will be
removed in future versions.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="user_ids",
new_attr_name="users",
bot_api_version="7.2",
stacklevel=2,
)
return tuple(user.user_id for user in self.users)
class ChatShared(TelegramObject):
"""
@ -88,6 +169,17 @@ class ChatShared(TelegramObject):
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it is smaller than 52 bits, so a signed 64-bit integer or double-precision
float type are safe for storing this identifier.
title (:obj:`str`, optional): Title of the chat, if the title was requested by the bot.
.. versionadded:: NEXT.VERSION
username (:obj:`str`, optional): Username of the chat, if the username was requested by
the bot and available.
.. versionadded:: NEXT.VERSION
photo (Sequence[:class:`telegram.PhotoSize`], optional): Available sizes of the chat photo,
if the photo was requested by the bot
.. versionadded:: NEXT.VERSION
Attributes:
request_id (:obj:`int`): Identifier of the request.
@ -95,21 +187,127 @@ class ChatShared(TelegramObject):
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it is smaller than 52 bits, so a signed 64-bit integer or double-precision
float type are safe for storing this identifier.
title (:obj:`str`): Optional. Title of the chat, if the title was requested by the bot.
.. versionadded:: NEXT.VERSION
username (:obj:`str`): Optional. Username of the chat, if the username was requested by
the bot and available.
.. versionadded:: NEXT.VERSION
photo (Tuple[:class:`telegram.PhotoSize`]): Optional. Available sizes of the chat photo,
if the photo was requested by the bot
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("chat_id", "request_id")
__slots__ = ("chat_id", "photo", "request_id", "title", "username")
def __init__(
self,
request_id: int,
chat_id: int,
title: Optional[str] = None,
username: Optional[str] = None,
photo: Optional[Sequence[PhotoSize]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.request_id: int = request_id
self.chat_id: int = chat_id
self.title: Optional[str] = title
self.username: Optional[str] = username
self.photo: Optional[Tuple[PhotoSize, ...]] = parse_sequence_arg(photo)
self._id_attrs = (self.request_id, self.chat_id)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatShared"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
return super().de_json(data=data, bot=bot)
class SharedUser(TelegramObject):
"""
This object contains information about a user that was shared with the bot using a
:class:`telegram.KeyboardButtonRequestUsers` button.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`user_id` is equal.
.. versionadded:: NEXT.VERSION
Args:
user_id (:obj:`int`): Identifier of the shared user. This number may have 32 significant
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it has atmost 52 significant bits, so 64-bit integers or double-precision
float types are safe for storing these identifiers. The bot may not have access to the
user and could be unable to use this identifier, unless the user is already known to
the bot by some other means.
first_name (:obj:`str`, optional): First name of the user, if the name was requested by the
bot.
last_name (:obj:`str`, optional): Last name of the user, if the name was requested by the
bot.
username (:obj:`str`, optional): Username of the user, if the username was requested by the
bot.
photo (Sequence[:class:`telegram.PhotoSize`], optional): Available sizes of the chat photo,
if the photo was requested by the bot.
Attributes:
user_id (:obj:`int`): Identifier of the shared user. This number may have 32 significant
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it has atmost 52 significant bits, so 64-bit integers or double-precision
float types are safe for storing these identifiers. The bot may not have access to the
user and could be unable to use this identifier, unless the user is already known to
the bot by some other means.
first_name (:obj:`str`): Optional. First name of the user, if the name was requested by the
bot.
last_name (:obj:`str`): Optional. Last name of the user, if the name was requested by the
bot.
username (:obj:`str`): Optional. Username of the user, if the username was requested by the
bot.
photo (Tuple[:class:`telegram.PhotoSize`]): Available sizes of the chat photo, if
the photo was requested by the bot. This list is empty if the photo was not requsted.
"""
__slots__ = ("first_name", "last_name", "photo", "user_id", "username")
def __init__(
self,
user_id: int,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
username: Optional[str] = None,
photo: Optional[Sequence[PhotoSize]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.user_id: int = user_id
self.first_name: Optional[str] = first_name
self.last_name: Optional[str] = last_name
self.username: Optional[str] = username
self.photo: Optional[Tuple[PhotoSize, ...]] = parse_sequence_arg(photo)
self._id_attrs = (self.user_id,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["SharedUser"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
return super().de_json(data=data, bot=bot)

View file

@ -21,6 +21,7 @@
from typing import TYPE_CHECKING, Final, List, Optional, Union
from telegram import constants
from telegram._business import BusinessConnection, BusinessMessagesDeleted
from telegram._callbackquery import CallbackQuery
from telegram._chatboost import ChatBoostRemoved, ChatBoostUpdated
from telegram._chatjoinrequest import ChatJoinRequest
@ -134,6 +135,28 @@ class Update(TelegramObject):
.. versionadded:: 20.8
business_connection (:class:`telegram.BusinessConnection`, optional): The bot was connected
to or disconnected from a business account, or a user edited an existing connection
with the bot.
.. versionadded:: NEXT.VERSION
business_message (:class:`telegram.Message`, optional): New non-service message
from a connected business account.
.. versionadded:: NEXT.VERSION
edited_business_message (:class:`telegram.Message`, optional): New version of a message
from a connected business account.
.. versionadded:: NEXT.VERSION
deleted_business_messages (:class:`telegram.BusinessMessagesDeleted`, optional): Messages
were deleted from a connected business account.
.. versionadded:: NEXT.VERSION
Attributes:
update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
certain positive number and increase sequentially. This ID becomes especially handy if
@ -219,6 +242,27 @@ class Update(TelegramObject):
with delay up to a few minutes.
.. versionadded:: 20.8
business_connection (:class:`telegram.BusinessConnection`): Optional. The bot was connected
to or disconnected from a business account, or a user edited an existing connection
with the bot.
.. versionadded:: NEXT.VERSION
business_message (:class:`telegram.Message`): Optional. New non-service message
from a connected business account.
.. versionadded:: NEXT.VERSION
edited_business_message (:class:`telegram.Message`): Optional. New version of a message
from a connected business account.
.. versionadded:: NEXT.VERSION
deleted_business_messages (:class:`telegram.BusinessMessagesDeleted`): Optional. Messages
were deleted from a connected business account.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
@ -226,12 +270,16 @@ class Update(TelegramObject):
"_effective_message",
"_effective_sender",
"_effective_user",
"business_connection",
"business_message",
"callback_query",
"channel_post",
"chat_boost",
"chat_join_request",
"chat_member",
"chosen_inline_result",
"deleted_business_messages",
"edited_business_message",
"edited_channel_post",
"edited_message",
"inline_query",
@ -319,6 +367,22 @@ class Update(TelegramObject):
""":const:`telegram.constants.UpdateType.MESSAGE_REACTION_COUNT`
.. versionadded:: 20.8"""
BUSINESS_CONNECTION: Final[str] = constants.UpdateType.BUSINESS_CONNECTION
""":const:`telegram.constants.UpdateType.BUSINESS_CONNECTION`
.. versionadded:: NEXT.VERSION"""
BUSINESS_MESSAGE: Final[str] = constants.UpdateType.BUSINESS_MESSAGE
""":const:`telegram.constants.UpdateType.BUSINESS_MESSAGE`
.. versionadded:: NEXT.VERSION"""
EDITED_BUSINESS_MESSAGE: Final[str] = constants.UpdateType.EDITED_BUSINESS_MESSAGE
""":const:`telegram.constants.UpdateType.EDITED_BUSINESS_MESSAGE`
.. versionadded:: NEXT.VERSION"""
DELETED_BUSINESS_MESSAGES: Final[str] = constants.UpdateType.DELETED_BUSINESS_MESSAGES
""":const:`telegram.constants.UpdateType.DELETED_BUSINESS_MESSAGES`
.. versionadded:: NEXT.VERSION"""
ALL_TYPES: Final[List[str]] = list(constants.UpdateType)
"""List[:obj:`str`]: A list of all available update types.
@ -345,6 +409,10 @@ class Update(TelegramObject):
removed_chat_boost: Optional[ChatBoostRemoved] = None,
message_reaction: Optional[MessageReactionUpdated] = None,
message_reaction_count: Optional[MessageReactionCountUpdated] = None,
business_connection: Optional[BusinessConnection] = None,
business_message: Optional[Message] = None,
edited_business_message: Optional[Message] = None,
deleted_business_messages: Optional[BusinessMessagesDeleted] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -370,6 +438,12 @@ class Update(TelegramObject):
self.removed_chat_boost: Optional[ChatBoostRemoved] = removed_chat_boost
self.message_reaction: Optional[MessageReactionUpdated] = message_reaction
self.message_reaction_count: Optional[MessageReactionCountUpdated] = message_reaction_count
self.business_connection: Optional[BusinessConnection] = business_connection
self.business_message: Optional[Message] = business_message
self.edited_business_message: Optional[Message] = edited_business_message
self.deleted_business_messages: Optional[BusinessMessagesDeleted] = (
deleted_business_messages
)
self._effective_user: Optional[User] = None
self._effective_sender: Optional[Union["User", "Chat"]] = None
@ -393,9 +467,14 @@ class Update(TelegramObject):
* :attr:`chat_boost`
* :attr:`removed_chat_boost`
* :attr:`message_reaction_count`
* :attr:`deleted_business_messages`
is present.
.. versionchanged:: NEXT.VERSION
This property now also considers :attr:`business_connection`, :attr:`business_message`
and :attr:`edited_business_message`.
Example:
* If :attr:`message` is present, this will give
:attr:`telegram.Message.from_user`.
@ -443,6 +522,15 @@ class Update(TelegramObject):
elif self.message_reaction:
user = self.message_reaction.user
elif self.business_message:
user = self.business_message.from_user
elif self.edited_business_message:
user = self.edited_business_message.from_user
elif self.business_connection:
user = self.business_connection.user
self._effective_user = user
return user
@ -463,6 +551,7 @@ class Update(TelegramObject):
* :attr:`chat_boost`
* :attr:`removed_chat_boost`
* :attr:`message_reaction_count`
* :attr:`deleted_business_messages`
is present.
@ -482,7 +571,12 @@ class Update(TelegramObject):
sender: Optional[Union["User", "Chat"]] = None
if message := (
self.message or self.edited_message or self.channel_post or self.edited_channel_post
self.message
or self.edited_message
or self.channel_post
or self.edited_channel_post
or self.business_message
or self.edited_business_message
):
sender = message.sender_chat
@ -506,8 +600,12 @@ class Update(TelegramObject):
If no chat is associated with this update, this gives :obj:`None`.
This is the case, if :attr:`inline_query`,
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
:attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll` or
:attr:`poll_answer` is present.
:attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll`,
:attr:`poll_answer`, or :attr:`business_connection` is present.
.. versionchanged:: NEXT.VERSION
This property now also considers :attr:`business_message`,
:attr:`edited_business_message`, and :attr:`deleted_business_messages`.
Example:
If :attr:`message` is present, this will give :attr:`telegram.Message.chat`.
@ -554,6 +652,15 @@ class Update(TelegramObject):
elif self.message_reaction_count:
chat = self.message_reaction_count.chat
elif self.business_message:
chat = self.business_message.chat
elif self.edited_business_message:
chat = self.edited_business_message.chat
elif self.deleted_business_messages:
chat = self.deleted_business_messages.chat
self._effective_chat = chat
return chat
@ -566,6 +673,10 @@ class Update(TelegramObject):
:attr:`callback_query` (i.e. :attr:`telegram.CallbackQuery.message`) or :obj:`None`, if
none of those are present.
.. versionchanged:: NEXT.VERSION
This property now also considers :attr:`business_message`, and
:attr:`edited_business_message`.
Tip:
This property will only ever return objects of type :class:`telegram.Message` or
:obj:`None`, never :class:`telegram.MaybeInaccessibleMessage` or
@ -608,6 +719,12 @@ class Update(TelegramObject):
elif self.edited_channel_post:
message = self.edited_channel_post
elif self.business_message:
message = self.business_message
elif self.edited_business_message:
message = self.edited_business_message
self._effective_message = message
return message
@ -643,5 +760,13 @@ class Update(TelegramObject):
data["message_reaction_count"] = MessageReactionCountUpdated.de_json(
data.get("message_reaction_count"), bot
)
data["business_connection"] = BusinessConnection.de_json(
data.get("business_connection"), bot
)
data["business_message"] = Message.de_json(data.get("business_message"), bot)
data["edited_business_message"] = Message.de_json(data.get("edited_business_message"), bot)
data["deleted_business_messages"] = BusinessMessagesDeleted.de_json(
data.get("deleted_business_messages"), bot
)
return super().de_json(data=data, bot=bot)

View file

@ -78,11 +78,11 @@ class User(TelegramObject):
username (:obj:`str`, optional): User's or bot's username.
language_code (:obj:`str`, optional): IETF language tag of the user's language.
can_join_groups (:obj:`str`, optional): :obj:`True`, if the bot can be invited to groups.
Returned only in :attr:`telegram.Bot.get_me` requests.
Returned only in :meth:`telegram.Bot.get_me`.
can_read_all_group_messages (:obj:`str`, optional): :obj:`True`, if privacy mode is
disabled for the bot. Returned only in :attr:`telegram.Bot.get_me` requests.
disabled for the bot. Returned only in :meth:`telegram.Bot.get_me`.
supports_inline_queries (:obj:`str`, optional): :obj:`True`, if the bot supports inline
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
queries. Returned only in :meth:`telegram.Bot.get_me`.
is_premium (:obj:`bool`, optional): :obj:`True`, if this user is a Telegram Premium user.
@ -91,6 +91,12 @@ class User(TelegramObject):
the bot to the attachment menu.
.. versionadded:: 20.0
can_connect_to_business (:obj:`bool`, optional): :obj:`True`, if the bot can be connected
to a Telegram Business account to receive its messages. Returned only in
:meth:`telegram.Bot.get_me`.
.. versionadded:: NEXT.VERSION
Attributes:
id (:obj:`int`): Unique identifier for this user or bot.
is_bot (:obj:`bool`): :obj:`True`, if this user is a bot.
@ -112,6 +118,11 @@ class User(TelegramObject):
the bot to the attachment menu.
.. versionadded:: 20.0
can_connect_to_business (:obj:`bool`): Optional. :obj:`True`, if the bot can be connected
to a Telegram Business account to receive its messages. Returned only in
:meth:`telegram.Bot.get_me`.
.. versionadded:: NEXT.VERSION
.. |user_chat_id_note| replace:: This shortcuts build on the assumption that :attr:`User.id`
coincides with the :attr:`Chat.id` of the private chat with the user. This has been the
case so far, but Telegram does not guarantee that this stays this way.
@ -119,6 +130,7 @@ class User(TelegramObject):
__slots__ = (
"added_to_attachment_menu",
"can_connect_to_business",
"can_join_groups",
"can_read_all_group_messages",
"first_name",
@ -144,6 +156,7 @@ class User(TelegramObject):
supports_inline_queries: Optional[bool] = None,
is_premium: Optional[bool] = None,
added_to_attachment_menu: Optional[bool] = None,
can_connect_to_business: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -161,6 +174,7 @@ class User(TelegramObject):
self.supports_inline_queries: Optional[bool] = supports_inline_queries
self.is_premium: Optional[bool] = is_premium
self.added_to_attachment_menu: Optional[bool] = added_to_attachment_menu
self.can_connect_to_business: Optional[bool] = can_connect_to_business
self._id_attrs = (self.id,)
@ -393,6 +407,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
disable_web_page_preview: Optional[bool] = None,
@ -435,6 +450,7 @@ class User(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def delete_message(
@ -513,6 +529,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -556,6 +573,7 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
business_connection_id=business_connection_id,
)
async def send_media_group(
@ -567,6 +585,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -610,6 +629,7 @@ class User(TelegramObject):
caption=caption,
parse_mode=parse_mode,
caption_entities=caption_entities,
business_connection_id=business_connection_id,
)
async def send_audio(
@ -627,6 +647,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -673,12 +694,14 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
)
async def send_chat_action(
self,
action: str,
message_thread_id: Optional[int] = None,
business_connection_id: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -708,6 +731,7 @@ class User(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
send_action = send_chat_action
@ -724,6 +748,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -766,6 +791,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_dice(
@ -776,6 +802,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -813,6 +840,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_document(
@ -828,6 +856,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -872,6 +901,7 @@ class User(TelegramObject):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_game(
@ -882,6 +912,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -919,6 +950,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_invoice(
@ -1031,6 +1063,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1075,6 +1108,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_animation(
@ -1093,6 +1127,7 @@ class User(TelegramObject):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1140,6 +1175,7 @@ class User(TelegramObject):
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
)
async def send_sticker(
@ -1151,6 +1187,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1189,6 +1226,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
business_connection_id=business_connection_id,
)
async def send_video(
@ -1208,6 +1246,7 @@ class User(TelegramObject):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1256,6 +1295,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
business_connection_id=business_connection_id,
)
async def send_venue(
@ -1273,6 +1313,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1319,6 +1360,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_video_note(
@ -1332,6 +1374,7 @@ class User(TelegramObject):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1374,6 +1417,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
business_connection_id=business_connection_id,
)
async def send_voice(
@ -1388,6 +1432,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1431,6 +1476,7 @@ class User(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_poll(
@ -1452,6 +1498,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -1500,6 +1547,7 @@ class User(TelegramObject):
explanation_entities=explanation_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
)
async def send_copy(

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=1)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=2)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@ -1710,6 +1710,11 @@ class MessageType(StringEnum):
.. versionadded:: 21.0
"""
BUSINESS_CONNECTION_ID = "business_connection_id"
""":obj:`str`: Messages with :attr:`telegram.Message.business_connection_id`.
.. versionadded:: NEXT.VERSION
"""
CHANNEL_CHAT_CREATED = "channel_chat_created"
""":obj:`str`: Messages with :attr:`telegram.Message.channel_chat_created`."""
CHAT_SHARED = "chat_shared"
@ -1817,6 +1822,11 @@ class MessageType(StringEnum):
.. versionadded:: 21.0
"""
SENDER_BUSINESS_BOT = "sender_business_bot"
""":obj:`str`: Messages with :attr:`telegram.Message.sender_business_bot`.
.. versionadded:: NEXT.VERSION
"""
STICKER = "sticker"
""":obj:`str`: Messages with :attr:`telegram.Message.sticker`."""
STORY = "story"
@ -2312,6 +2322,9 @@ class StickerSetLimit(IntEnum):
MAX_ANIMATED_STICKERS = 50
""":obj:`int`: Maximum number of stickers allowed in an animated or video sticker set, as given
in :meth:`telegram.Bot.add_sticker_to_set`.
.. deprecated:: NEXT.VERSION
The animated sticker limit is now 120, the same as :attr:`MAX_STATIC_STICKERS`.
"""
MAX_STATIC_STICKERS = 120
""":obj:`int`: Maximum number of stickers allowed in a static sticker set, as given in
@ -2504,6 +2517,26 @@ class UpdateType(StringEnum):
.. versionadded:: 20.8
"""
BUSINESS_CONNECTION = "business_connection"
""":obj:`str`: Updates with :attr:`telegram.Update.business_connection`.
.. versionadded:: NEXT.VERSION
"""
BUSINESS_MESSAGE = "business_message"
""":obj:`str`: Updates with :attr:`telegram.Update.business_message`.
.. versionadded:: NEXT.VERSION
"""
EDITED_BUSINESS_MESSAGE = "edited_business_message"
""":obj:`str`: Updates with :attr:`telegram.Update.edited_business_message`.
.. versionadded:: NEXT.VERSION
"""
DELETED_BUSINESS_MESSAGES = "deleted_business_messages"
""":obj:`str`: Updates with :attr:`telegram.Update.deleted_business_messages`.
.. versionadded:: NEXT.VERSION
"""
class InvoiceLimit(IntEnum):

View file

@ -27,6 +27,8 @@ __all__ = (
"BasePersistence",
"BaseRateLimiter",
"BaseUpdateProcessor",
"BusinessConnectionHandler",
"BusinessMessagesDeletedHandler",
"CallbackContext",
"CallbackDataCache",
"CallbackQueryHandler",
@ -75,6 +77,8 @@ from ._defaults import Defaults
from ._dictpersistence import DictPersistence
from ._extbot import ExtBot
from ._handlers.basehandler import BaseHandler
from ._handlers.businessconnectionhandler import BusinessConnectionHandler
from ._handlers.businessmessagesdeletedhandler import BusinessMessagesDeletedHandler
from ._handlers.callbackqueryhandler import CallbackQueryHandler
from ._handlers.chatboosthandler import ChatBoostHandler
from ._handlers.chatjoinrequesthandler import ChatJoinRequestHandler

View file

@ -48,6 +48,7 @@ from telegram import (
BotDescription,
BotName,
BotShortDescription,
BusinessConnection,
CallbackQuery,
Chat,
ChatAdministratorRights,
@ -571,6 +572,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Optional[Sequence["MessageEntity"]] = None,
link_preview_options: ODVInput["LinkPreviewOptions"] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -601,6 +603,7 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
if isinstance(result, Message):
self._insert_callback_data(result)
@ -1182,7 +1185,7 @@ class ExtBot(Bot, Generic[RLARGS]):
name: str,
title: str,
stickers: Sequence["InputSticker"],
sticker_format: str,
sticker_format: Optional[str] = None,
sticker_type: Optional[str] = None,
needs_repainting: Optional[bool] = None,
*,
@ -2355,6 +2358,7 @@ class ExtBot(Bot, Generic[RLARGS]):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2388,6 +2392,7 @@ class ExtBot(Bot, Generic[RLARGS]):
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
business_connection_id=business_connection_id,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@ -2408,6 +2413,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2424,6 +2430,7 @@ class ExtBot(Bot, Generic[RLARGS]):
audio=audio,
duration=duration,
performer=performer,
business_connection_id=business_connection_id,
title=title,
caption=caption,
disable_notification=disable_notification,
@ -2449,6 +2456,7 @@ class ExtBot(Bot, Generic[RLARGS]):
chat_id: Union[str, int],
action: str,
message_thread_id: Optional[int] = None,
business_connection_id: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2459,6 +2467,7 @@ class ExtBot(Bot, Generic[RLARGS]):
) -> bool:
return await super().send_chat_action(
chat_id=chat_id,
business_connection_id=business_connection_id,
action=action,
message_thread_id=message_thread_id,
read_timeout=read_timeout,
@ -2480,6 +2489,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2509,6 +2519,7 @@ class ExtBot(Bot, Generic[RLARGS]):
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
business_connection_id=business_connection_id,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@ -2521,6 +2532,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2534,6 +2546,7 @@ class ExtBot(Bot, Generic[RLARGS]):
return await super().send_dice(
chat_id=chat_id,
disable_notification=disable_notification,
business_connection_id=business_connection_id,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
emoji=emoji,
@ -2562,6 +2575,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2585,6 +2599,7 @@ class ExtBot(Bot, Generic[RLARGS]):
allow_sending_without_reply=allow_sending_without_reply,
caption_entities=caption_entities,
protect_content=protect_content,
business_connection_id=business_connection_id,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
reply_parameters=reply_parameters,
@ -2605,6 +2620,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2621,6 +2637,7 @@ class ExtBot(Bot, Generic[RLARGS]):
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
business_connection_id=business_connection_id,
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
@ -2722,6 +2739,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2752,6 +2770,7 @@ class ExtBot(Bot, Generic[RLARGS]):
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
business_connection_id=business_connection_id,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@ -2766,6 +2785,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2794,6 +2814,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
caption=caption,
business_connection_id=business_connection_id,
parse_mode=parse_mode,
caption_entities=caption_entities,
)
@ -2810,6 +2831,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
disable_web_page_preview: Optional[bool] = None,
reply_to_message_id: Optional[int] = None,
@ -2828,6 +2850,7 @@ class ExtBot(Bot, Generic[RLARGS]):
entities=entities,
disable_web_page_preview=disable_web_page_preview,
disable_notification=disable_notification,
business_connection_id=business_connection_id,
protect_content=protect_content,
message_thread_id=message_thread_id,
reply_to_message_id=reply_to_message_id,
@ -2855,6 +2878,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
has_spoiler: Optional[bool] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2881,6 +2905,7 @@ class ExtBot(Bot, Generic[RLARGS]):
has_spoiler=has_spoiler,
reply_parameters=reply_parameters,
filename=filename,
business_connection_id=business_connection_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@ -2908,6 +2933,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2936,6 +2962,7 @@ class ExtBot(Bot, Generic[RLARGS]):
close_date=close_date,
allow_sending_without_reply=allow_sending_without_reply,
explanation_entities=explanation_entities,
business_connection_id=business_connection_id,
protect_content=protect_content,
message_thread_id=message_thread_id,
reply_parameters=reply_parameters,
@ -2956,6 +2983,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
emoji: Optional[str] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -2972,6 +3000,7 @@ class ExtBot(Bot, Generic[RLARGS]):
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup,
business_connection_id=business_connection_id,
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
@ -3000,6 +3029,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3026,6 +3056,7 @@ class ExtBot(Bot, Generic[RLARGS]):
google_place_type=google_place_type,
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
business_connection_id=business_connection_id,
message_thread_id=message_thread_id,
reply_parameters=reply_parameters,
venue=venue,
@ -3054,6 +3085,7 @@ class ExtBot(Bot, Generic[RLARGS]):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3081,6 +3113,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
business_connection_id=business_connection_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
filename=filename,
@ -3104,6 +3137,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[FileInput] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3134,6 +3168,7 @@ 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),
business_connection_id=business_connection_id,
)
async def send_voice(
@ -3149,6 +3184,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
reply_parameters: Optional["ReplyParameters"] = None,
business_connection_id: Optional[str] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@ -3180,6 +3216,7 @@ 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),
business_connection_id=business_connection_id,
)
async def set_chat_administrator_custom_title(
@ -3466,6 +3503,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
name: str,
user_id: int,
format: str, # pylint: disable=redefined-builtin
thumbnail: Optional[FileInput] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -3479,6 +3517,7 @@ class ExtBot(Bot, Generic[RLARGS]):
name=name,
user_id=user_id,
thumbnail=thumbnail,
format=format,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@ -4002,6 +4041,52 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_business_connection(
self,
business_connection_id: str,
*,
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,
) -> BusinessConnection:
return await super().get_business_connection(
business_connection_id=business_connection_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 replace_sticker_in_set(
self,
user_id: int,
name: str,
old_sticker: str,
sticker: "InputSticker",
*,
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().replace_sticker_in_set(
user_id=user_id,
name=name,
old_sticker=old_sticker,
sticker=sticker,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
# updated camelCase aliases
getMe = get_me
sendMessage = send_message
@ -4121,3 +4206,5 @@ class ExtBot(Bot, Generic[RLARGS]):
unpinAllGeneralForumTopicMessages = unpin_all_general_forum_topic_messages
getUserChatBoosts = get_user_chat_boosts
setMessageReaction = set_message_reaction
getBusinessConnection = get_business_connection
replaceStickerInSet = replace_sticker_in_set

View file

@ -0,0 +1,95 @@
#!/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 the BusinessConnectionHandler class."""
from typing import Optional, TypeVar
from telegram import Update
from telegram._utils.defaultvalue import DEFAULT_TRUE
from telegram._utils.types import SCT, DVType
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils._update_parsing import parse_chat_id, parse_username
from telegram.ext._utils.types import CCT, HandlerCallback
RT = TypeVar("RT")
class BusinessConnectionHandler(BaseHandler[Update, CCT]):
"""Handler class to handle Telegram
:attr:`Business Connections <telegram.Update.business_connection>`.
.. versionadded:: NEXT.VERSION
Args:
callback (:term:`coroutine function`): The callback function for this handler. Will be
called when :meth:`check_update` has determined that an update should be processed by
this handler. Callback signature::
async def callback(update: Update, context: CallbackContext)
user_id (:obj:`int` | Collection[:obj:`int`], optional): Filters requests to allow only
those which are from the specified user ID(s).
username (:obj:`str` | Collection[:obj:`str`], optional): Filters requests to allow only
those which are from the specified username(s).
block (:obj:`bool`, optional): Determines whether the return value of the callback should
be awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`.
.. seealso:: :wiki:`Concurrency`
Attributes:
callback (:term:`coroutine function`): The callback function for this handler.
block (:obj:`bool`): Determines whether the return value of the callback should be
awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`.
"""
__slots__ = (
"_user_ids",
"_usernames",
)
def __init__(
self,
callback: HandlerCallback[Update, CCT, RT],
user_id: Optional[SCT[int]] = None,
username: Optional[SCT[str]] = None,
block: DVType[bool] = DEFAULT_TRUE,
):
super().__init__(callback, block=block)
self._user_ids = parse_chat_id(user_id)
self._usernames = parse_username(username)
def check_update(self, update: object) -> bool:
"""Determines whether an update should be passed to this handler's :attr:`callback`.
Args:
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
Returns:
:obj:`bool`
"""
if isinstance(update, Update) and update.business_connection:
if not self._user_ids and not self._usernames:
return True
if update.business_connection.user.id in self._user_ids:
return True
return update.business_connection.user.username in self._usernames
return False

View file

@ -0,0 +1,95 @@
#!/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 the BusinessMessagesDeletedHandler class."""
from typing import Optional, TypeVar
from telegram import Update
from telegram._utils.defaultvalue import DEFAULT_TRUE
from telegram._utils.types import SCT, DVType
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils._update_parsing import parse_chat_id, parse_username
from telegram.ext._utils.types import CCT, HandlerCallback
RT = TypeVar("RT")
class BusinessMessagesDeletedHandler(BaseHandler[Update, CCT]):
"""Handler class to handle
:attr:`deleted Telegram Business messages <telegram.Update.deleted_business_messages>`.
.. versionadded:: NEXT.VERSION
Args:
callback (:term:`coroutine function`): The callback function for this handler. Will be
called when :meth:`check_update` has determined that an update should be processed by
this handler. Callback signature::
async def callback(update: Update, context: CallbackContext)
chat_id (:obj:`int` | Collection[:obj:`int`], optional): Filters requests to allow only
those which are from the specified chat ID(s).
username (:obj:`str` | Collection[:obj:`str`], optional): Filters requests to allow only
those which are from the specified username(s).
block (:obj:`bool`, optional): Determines whether the return value of the callback should
be awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`.
.. seealso:: :wiki:`Concurrency`
Attributes:
callback (:term:`coroutine function`): The callback function for this handler.
block (:obj:`bool`): Determines whether the return value of the callback should be
awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`.
"""
__slots__ = (
"_chat_ids",
"_usernames",
)
def __init__(
self,
callback: HandlerCallback[Update, CCT, RT],
chat_id: Optional[SCT[int]] = None,
username: Optional[SCT[str]] = None,
block: DVType[bool] = DEFAULT_TRUE,
):
super().__init__(callback, block=block)
self._chat_ids = parse_chat_id(chat_id)
self._usernames = parse_username(username)
def check_update(self, update: object) -> bool:
"""Determines whether an update should be passed to this handler's :attr:`callback`.
Args:
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
Returns:
:obj:`bool`
"""
if isinstance(update, Update) and update.deleted_business_messages:
if not self._chat_ids and not self._usernames:
return True
if update.deleted_business_messages.chat.id in self._chat_ids:
return True
return update.deleted_business_messages.chat.username in self._usernames
return False

View file

@ -54,6 +54,7 @@ __all__ = (
"HAS_PROTECTED_CONTENT",
"INVOICE",
"IS_AUTOMATIC_FORWARD",
"IS_FROM_OFFLINE",
"IS_TOPIC_MESSAGE",
"LOCATION",
"PASSPORT_DATA",
@ -272,20 +273,28 @@ class BaseFilter:
def check_update(self, update: Update) -> Optional[Union[bool, FilterDataDict]]:
"""Checks if the specified update should be handled by this filter.
.. versionchanged:: NEXT.VERSION
This filter now also returns :obj:`True` if the update contains
:attr:`~telegram.Update.business_message`
or :attr:`~telegram.Update.edited_business_message`.
Args:
update (:class:`telegram.Update`): The update to check.
Returns:
:obj:`bool`: :obj:`True` if the update contains one of
:attr:`~telegram.Update.channel_post`, :attr:`~telegram.Update.message`,
:attr:`~telegram.Update.edited_channel_post` or
:attr:`~telegram.Update.edited_message`, :obj:`False` otherwise.
:attr:`~telegram.Update.edited_channel_post`,
:attr:`~telegram.Update.edited_message`, :attr:`telegram.Update.business_message`,
:attr:`telegram.Update.edited_business_message`, or :obj:`False` otherwise.
"""
return bool( # Only message updates should be handled.
update.channel_post
update.channel_post # pylint: disable=too-many-boolean-expressions
or update.message
or update.edited_channel_post
or update.edited_message
or update.business_message
or update.edited_business_message
)
@ -1554,6 +1563,20 @@ IS_TOPIC_MESSAGE = _IsTopicMessage(name="filters.IS_TOPIC_MESSAGE")
"""
class _IsFromOffline(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.is_from_offline)
IS_FROM_OFFLINE = _IsFromOffline(name="filters.IS_FROM_OFFLINE")
"""Messages that contain :attr:`telegram.Message.is_from_offline`.
.. versionadded:: NEXT.VERSION
"""
class Language(MessageFilter):
"""Filters messages to only allow those which are from users with a certain language code.
@ -2486,13 +2509,21 @@ class UpdateType:
__slots__ = ()
def filter(self, update: Update) -> bool:
return update.edited_message is not None or update.edited_channel_post is not None
return (
update.edited_message is not None
or update.edited_channel_post is not None
or update.edited_business_message is not None
)
EDITED = _Edited(name="filters.UpdateType.EDITED")
"""Updates with either :attr:`telegram.Update.edited_message` or
:attr:`telegram.Update.edited_channel_post`.
"""Updates with :attr:`telegram.Update.edited_message`,
:attr:`telegram.Update.edited_channel_post`, or
:attr:`telegram.Update.edited_business_message`.
.. versionadded:: 20.0
.. versionchanged:: NEXT.VERSION
Added :attr:`telegram.Update.edited_business_message` to the filter.
"""
class _EditedChannelPost(UpdateFilter):
@ -2530,7 +2561,48 @@ class UpdateType:
MESSAGES = _Messages(name="filters.UpdateType.MESSAGES")
"""Updates with either :attr:`telegram.Update.message` or
:attr:`telegram.Update.edited_message`."""
:attr:`telegram.Update.edited_message`.
"""
class _BusinessMessage(UpdateFilter):
__slots__ = ()
def filter(self, update: Update) -> bool:
return update.business_message is not None
BUSINESS_MESSAGE = _BusinessMessage(name="filters.UpdateType.BUSINESS_MESSAGE")
"""Updates with :attr:`telegram.Update.business_message`.
.. versionadded:: NEXT.VERSION"""
class _EditedBusinessMessage(UpdateFilter):
__slots__ = ()
def filter(self, update: Update) -> bool:
return update.edited_business_message is not None
EDITED_BUSINESS_MESSAGE = _EditedBusinessMessage(
name="filters.UpdateType.EDITED_BUSINESS_MESSAGE"
)
"""Updates with :attr:`telegram.Update.edited_business_message`.
.. versionadded:: NEXT.VERSION
"""
class _BusinessMessages(UpdateFilter):
__slots__ = ()
def filter(self, update: Update) -> bool:
return (
update.business_message is not None or update.edited_business_message is not None
)
BUSINESS_MESSAGES = _BusinessMessages(name="filters.UpdateType.BUSINESS_MESSAGES")
"""Updates with either :attr:`telegram.Update.business_message` or
:attr:`telegram.Update.edited_business_message`.
.. versionadded:: NEXT.VERSION
"""
class User(_ChatUserBaseFilter):
@ -2675,6 +2747,8 @@ class ViaBot(_ChatUserBaseFilter):
Examples:
``MessageHandler(filters.ViaBot(1234), callback_method)``
.. seealso:: :attr:`~telegram.ext.filters.VIA_BOT`
Args:
bot_id(:obj:`int` | Collection[:obj:`int`], optional): Which bot ID(s) to
allow through.
@ -2756,7 +2830,9 @@ class _ViaBot(MessageFilter):
VIA_BOT = _ViaBot(name="filters.VIA_BOT")
"""This filter filters for message that were sent via *any* bot."""
"""This filter filters for message that were sent via *any* bot.
.. seealso:: :class:`~telegram.ext.filters.ViaBot`"""
class _Video(MessageFilter):

View file

@ -33,6 +33,7 @@ def input_sticker():
emoji_list=TestInputStickerBase.emoji_list,
mask_position=TestInputStickerBase.mask_position,
keywords=TestInputStickerBase.keywords,
format=TestInputStickerBase.format,
)
@ -41,9 +42,10 @@ class TestInputStickerBase:
emoji_list = ("👍", "👎")
mask_position = MaskPosition("forehead", 0.5, 0.5, 0.5)
keywords = ("thumbsup", "thumbsdown")
format = "static"
class TestInputStickerNoRequest(TestInputStickerBase):
class TestInputStickerWithoutRequest(TestInputStickerBase):
def test_slot_behaviour(self, input_sticker):
inst = input_sticker
for attr in inst.__slots__:
@ -56,11 +58,12 @@ class TestInputStickerNoRequest(TestInputStickerBase):
assert input_sticker.emoji_list == self.emoji_list
assert input_sticker.mask_position == self.mask_position
assert input_sticker.keywords == self.keywords
assert input_sticker.format == self.format
def test_attributes_tuple(self, input_sticker):
assert isinstance(input_sticker.keywords, tuple)
assert isinstance(input_sticker.emoji_list, tuple)
a = InputSticker("sticker", ["emoji"])
a = InputSticker("sticker", ["emoji"], "static")
assert isinstance(a.emoji_list, tuple)
assert a.keywords == ()
@ -72,9 +75,10 @@ class TestInputStickerNoRequest(TestInputStickerBase):
assert input_sticker_dict["emoji_list"] == list(input_sticker.emoji_list)
assert input_sticker_dict["mask_position"] == input_sticker.mask_position.to_dict()
assert input_sticker_dict["keywords"] == list(input_sticker.keywords)
assert input_sticker_dict["format"] == input_sticker.format
def test_with_sticker_input_types(self, video_sticker_file): # noqa: F811
sticker = InputSticker(sticker=video_sticker_file, emoji_list=["👍"])
sticker = InputSticker(sticker=video_sticker_file, emoji_list=["👍"], format="video")
assert isinstance(sticker.sticker, InputFile)
sticker = InputSticker(data_file("telegram_video_sticker.webm"), ["👍"])
sticker = InputSticker(data_file("telegram_video_sticker.webm"), ["👍"], "video")
assert sticker.sticker == data_file("telegram_video_sticker.webm").as_uri()

View file

@ -39,6 +39,7 @@ from telegram import (
from telegram.constants import ParseMode, StickerFormat, StickerType
from telegram.error import BadRequest, TelegramError
from telegram.request import RequestData
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.bot_method_checks import (
check_defaults_handling,
check_shortcut_call,
@ -471,7 +472,6 @@ class TestStickerWithRequest(TestStickerBase):
assert protected.has_protected_content
assert not unprotected.has_protected_content
@pytest.mark.xfail(reason="API 7.2 incompatibility, see #4181")
async def test_premium_animation(self, bot):
# testing animation sucks a bit since we can't create a premium sticker. What we can do is
# get a sticker set which includes a premium sticker and check that specific one.
@ -489,7 +489,6 @@ class TestStickerWithRequest(TestStickerBase):
}
assert premium_sticker.premium_animation.to_dict() == premium_sticker_dict
@pytest.mark.xfail(reason="API 7.2 incompatibility, see #4181")
async def test_custom_emoji(self, bot):
# testing custom emoji stickers is as much of an annoyance as the premium animation, see
# in test_premium_animation
@ -528,7 +527,6 @@ class TestStickerWithRequest(TestStickerBase):
@pytest.fixture()
async def sticker_set(bot):
pytest.xfail(reason="API 7.2 incompatibility, see #4181")
ss = await bot.get_sticker_set(f"test_by_{bot.username}")
if len(ss.stickers) > 100:
try:
@ -543,7 +541,6 @@ async def sticker_set(bot):
@pytest.fixture()
async def animated_sticker_set(bot):
pytest.xfail(reason="API 7.2 incompatibility, see #4181")
ss = await bot.get_sticker_set(f"animated_test_by_{bot.username}")
if len(ss.stickers) > 100:
try:
@ -578,8 +575,6 @@ def sticker_set_thumb_file():
class TestStickerSetBase:
title = "Test stickers"
is_animated = True
is_video = True
stickers = [Sticker("file_id", "file_un_id", 512, 512, True, True, Sticker.REGULAR)]
name = "NOTAREALNAME"
sticker_type = Sticker.REGULAR
@ -588,7 +583,7 @@ class TestStickerSetBase:
class TestStickerSetWithoutRequest(TestStickerSetBase):
def test_slot_behaviour(self):
inst = StickerSet("this", "is", True, self.stickers, True, "not")
inst = StickerSet("this", "is", self.stickers, "not")
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"
@ -598,8 +593,6 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
json_dict = {
"name": name,
"title": self.title,
"is_animated": self.is_animated,
"is_video": self.is_video,
"stickers": [x.to_dict() for x in self.stickers],
"thumbnail": sticker.thumbnail.to_dict(),
"sticker_type": self.sticker_type,
@ -609,8 +602,6 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
assert sticker_set.name == name
assert sticker_set.title == self.title
assert sticker_set.is_animated == self.is_animated
assert sticker_set.is_video == self.is_video
assert sticker_set.stickers == tuple(self.stickers)
assert sticker_set.thumbnail == sticker.thumbnail
assert sticker_set.sticker_type == self.sticker_type
@ -622,8 +613,6 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
assert isinstance(sticker_set_dict, dict)
assert sticker_set_dict["name"] == sticker_set.name
assert sticker_set_dict["title"] == sticker_set.title
assert sticker_set_dict["is_animated"] == sticker_set.is_animated
assert sticker_set_dict["is_video"] == sticker_set.is_video
assert sticker_set_dict["stickers"][0] == sticker_set.stickers[0].to_dict()
assert sticker_set_dict["thumbnail"] == sticker_set.thumbnail.to_dict()
assert sticker_set_dict["sticker_type"] == sticker_set.sticker_type
@ -632,26 +621,20 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
a = StickerSet(
self.name,
self.title,
self.is_animated,
self.stickers,
self.is_video,
self.sticker_type,
)
b = StickerSet(
self.name,
self.title,
self.is_animated,
self.stickers,
self.is_video,
self.sticker_type,
)
c = StickerSet(self.name, "title", False, [], True, Sticker.CUSTOM_EMOJI)
c = StickerSet(self.name, "title", [], Sticker.CUSTOM_EMOJI)
d = StickerSet(
"blah",
self.title,
self.is_animated,
self.stickers,
self.is_video,
self.sticker_type,
)
e = Audio(self.name, "", 0, None, None)
@ -689,7 +672,9 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.upload_sticker_file(chat_id, sticker=file, sticker_format="static")
await bot.upload_sticker_file(
chat_id, sticker=file, sticker_format=StickerFormat.STATIC
)
assert test_flag
finally:
bot._local_mode = False
@ -719,8 +704,7 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
chat_id,
"name",
"title",
stickers=[InputSticker(file, emoji_list=["emoji"])],
sticker_format=StickerFormat.STATIC,
stickers=[InputSticker(file, emoji_list=["emoji"], format=StickerFormat.STATIC)],
)
assert test_flag
@ -759,7 +743,9 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.add_sticker_to_set(
chat_id, "name", sticker=InputSticker(sticker=file, emoji_list=["this"])
chat_id,
"name",
sticker=InputSticker(sticker=file, emoji_list=["this"], format="static"),
)
assert test_flag
@ -782,7 +768,7 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
test_flag = isinstance(data.get("thumbnail"), InputFile)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.set_sticker_set_thumbnail("name", chat_id, thumbnail=file)
await bot.set_sticker_set_thumbnail("name", chat_id, thumbnail=file, format="static")
assert test_flag
finally:
bot._local_mode = False
@ -798,9 +784,29 @@ class TestStickerSetWithoutRequest(TestStickerSetBase):
monkeypatch.setattr(sticker.get_bot(), "get_file", make_assertion)
assert await sticker.get_file()
async def test_create_new_sticker_set_format_arg_depr(
self, bot, chat_id, sticker_file, monkeypatch
):
async def make_assertion(*_, **kwargs):
pass
monkeypatch.setattr(bot, "_post", make_assertion)
with pytest.warns(PTBDeprecationWarning, match="`sticker_format` is deprecated"):
await bot.create_new_sticker_set(
chat_id,
"name",
"title",
stickers=sticker_file,
sticker_format="static",
)
async def test_deprecation_creation_args(self, recwarn):
with pytest.warns(PTBDeprecationWarning, match="The parameters `is_animated` and ") as w:
StickerSet("name", "title", [], "static", is_animated=True)
assert w[0].filename == __file__, "wrong stacklevel!"
@pytest.mark.xdist_group("stickerset")
@pytest.mark.xfail(reason="API 7.2 incompatibility, see #4181")
class TestStickerSetWithRequest:
async def test_create_sticker_set(
self, bot, chat_id, sticker_file, animated_sticker_file, video_sticker_file
@ -822,8 +828,11 @@ class TestStickerSetWithRequest:
chat_id,
name=sticker_set,
title="Sticker Test",
stickers=[InputSticker(sticker_file, emoji_list=["😄"])],
sticker_format=StickerFormat.STATIC,
stickers=[
InputSticker(
sticker_file, emoji_list=["😄"], format=StickerFormat.STATIC
)
],
)
assert s
elif sticker_set.startswith("animated"):
@ -831,8 +840,13 @@ class TestStickerSetWithRequest:
chat_id,
name=sticker_set,
title="Animated Test",
stickers=[InputSticker(animated_sticker_file, emoji_list=["😄"])],
sticker_format=StickerFormat.ANIMATED,
stickers=[
InputSticker(
animated_sticker_file,
emoji_list=["😄"],
format=StickerFormat.ANIMATED,
)
],
)
assert a
elif sticker_set.startswith("video"):
@ -840,8 +854,11 @@ class TestStickerSetWithRequest:
chat_id,
name=sticker_set,
title="Video Test",
stickers=[InputSticker(video_sticker_file, emoji_list=["😄"])],
sticker_format=StickerFormat.VIDEO,
stickers=[
InputSticker(
video_sticker_file, emoji_list=["😄"], format=StickerFormat.VIDEO
)
],
)
assert v
@ -855,8 +872,7 @@ class TestStickerSetWithRequest:
chat_id,
name=name,
title="Stickerset delete Test",
stickers=[InputSticker(sticker_file, emoji_list=["😄"])],
sticker_format=StickerFormat.STATIC,
stickers=[InputSticker(sticker_file, emoji_list=["😄"], format="static")],
)
# this prevents a second issue when calling delete too soon after creating the set leads
# to it failing as well
@ -875,8 +891,11 @@ class TestStickerSetWithRequest:
chat_id,
name=ss_name,
title="Custom Emoji Sticker Set",
stickers=[InputSticker(animated_sticker_file, emoji_list=["😄"])],
sticker_format=StickerFormat.ANIMATED,
stickers=[
InputSticker(
animated_sticker_file, emoji_list=["😄"], format=StickerFormat.ANIMATED
)
],
sticker_type=Sticker.CUSTOM_EMOJI,
)
assert await bot.set_custom_emoji_sticker_set_thumbnail(ss_name, "")
@ -895,7 +914,9 @@ class TestStickerSetWithRequest:
bot.add_sticker_to_set(
chat_id,
f"test_by_{bot.username}",
sticker=InputSticker(sticker=file.file_id, emoji_list=["😄"]),
sticker=InputSticker(
sticker=file.file_id, emoji_list=["😄"], format=StickerFormat.STATIC
),
),
bot.add_sticker_to_set( # Also test with file input and mask
chat_id,
@ -904,6 +925,7 @@ class TestStickerSetWithRequest:
sticker=sticker_file,
emoji_list=["😄"],
mask_position=MaskPosition(MaskPosition.EYES, -1, 1, 2),
format=StickerFormat.STATIC,
),
),
)
@ -915,7 +937,9 @@ class TestStickerSetWithRequest:
chat_id,
f"animated_test_by_{bot.username}",
sticker=InputSticker(
sticker=data_file("telegram_animated_sticker.tgs").open("rb"), emoji_list=["😄"]
sticker=data_file("telegram_animated_sticker.tgs").open("rb"),
emoji_list=["😄"],
format=StickerFormat.ANIMATED,
),
)
@ -925,7 +949,7 @@ class TestStickerSetWithRequest:
assert await bot.add_sticker_to_set(
chat_id,
f"video_test_by_{bot.username}",
sticker=InputSticker(sticker=f, emoji_list=["🤔"]),
sticker=InputSticker(sticker=f, emoji_list=["🤔"], format=StickerFormat.VIDEO),
)
# Test set_sticker_position_in_set
@ -948,7 +972,7 @@ class TestStickerSetWithRequest:
async def test_bot_methods_3_png(self, bot, chat_id, sticker_set_thumb_file):
await asyncio.sleep(1)
assert await bot.set_sticker_set_thumbnail(
f"test_by_{bot.username}", chat_id, sticker_set_thumb_file
f"test_by_{bot.username}", chat_id, format="static", thumbnail=sticker_set_thumb_file
)
async def test_bot_methods_3_tgs(
@ -958,8 +982,13 @@ class TestStickerSetWithRequest:
animated_test = f"animated_test_by_{bot.username}"
file_id = animated_sticker_set.stickers[-1].file_id
tasks = asyncio.gather(
bot.set_sticker_set_thumbnail(animated_test, chat_id, animated_sticker_file),
bot.set_sticker_set_thumbnail(animated_test, chat_id, file_id),
bot.set_sticker_set_thumbnail(
animated_test,
chat_id,
"animated",
thumbnail=animated_sticker_file,
),
bot.set_sticker_set_thumbnail(animated_test, chat_id, "animated", thumbnail=file_id),
)
assert all(await tasks)
@ -1042,6 +1071,19 @@ class TestStickerSetWithRequest:
file_id = video_sticker_set.stickers[-1].file_id
assert await bot.set_sticker_keywords(file_id, ["test", "test2"])
async def test_bot_methods_8_png(self, bot, sticker_set, sticker_file):
file_id = sticker_set.stickers[-1].file_id
assert await bot.replace_sticker_in_set(
bot.id,
f"test_by_{bot.username}",
file_id,
sticker=InputSticker(
sticker=sticker_file,
emoji_list=["😄"],
format=StickerFormat.STATIC,
),
)
@pytest.fixture(scope="module")
def mask_position():
@ -1112,7 +1154,6 @@ class TestMaskPositionWithoutRequest(TestMaskPositionBase):
assert hash(a) != hash(e)
@pytest.mark.xfail(reason="API 7.2 incompatibility, see #4181")
class TestMaskPositionWithRequest(TestMaskPositionBase):
async def test_create_new_mask_sticker_set(self, bot, chat_id, sticker_file, mask_position):
name = f"masks_by_{bot.username}"
@ -1132,9 +1173,9 @@ class TestMaskPositionWithRequest(TestMaskPositionBase):
emoji_list=["😔"],
mask_position=mask_position,
keywords=["sad"],
format=StickerFormat.STATIC,
)
],
sticker_format=StickerFormat.STATIC,
sticker_type=Sticker.MASK,
)
assert sticker_set

View file

@ -0,0 +1,173 @@
#!/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 asyncio
import datetime
import pytest
from telegram import (
Bot,
BusinessConnection,
CallbackQuery,
Chat,
ChosenInlineResult,
Message,
PreCheckoutQuery,
ShippingQuery,
Update,
User,
)
from telegram._utils.datetime import UTC
from telegram.ext import BusinessConnectionHandler, CallbackContext, JobQueue
from tests.auxil.slots import mro_slots
message = Message(1, None, Chat(1, ""), from_user=User(1, "", False), text="Text")
params = [
{"message": message},
{"edited_message": message},
{"callback_query": CallbackQuery(1, User(1, "", False), "chat", message=message)},
{"channel_post": message},
{"edited_channel_post": message},
{"chosen_inline_result": ChosenInlineResult("id", User(1, "", False), "")},
{"shipping_query": ShippingQuery("id", User(1, "", False), "", None)},
{"pre_checkout_query": PreCheckoutQuery("id", User(1, "", False), "", 0, "")},
{"callback_query": CallbackQuery(1, User(1, "", False), "chat")},
]
ids = (
"message",
"edited_message",
"callback_query",
"channel_post",
"edited_channel_post",
"chosen_inline_result",
"shipping_query",
"pre_checkout_query",
"callback_query_without_message",
)
@pytest.fixture(scope="class", params=params, ids=ids)
def false_update(request):
return Update(update_id=2, **request.param)
@pytest.fixture(scope="class")
def time():
return datetime.datetime.now(tz=UTC)
@pytest.fixture(scope="class")
def business_connection(bot):
bc = BusinessConnection(
id="1",
user_chat_id=1,
user=User(1, "name", username="user_a", is_bot=False),
date=datetime.datetime.now(tz=UTC),
can_reply=True,
is_enabled=True,
)
bc.set_bot(bot)
return bc
@pytest.fixture()
def business_connection_update(bot, business_connection):
return Update(0, business_connection=business_connection)
class TestBusinessConnectionHandler:
test_flag = False
def test_slot_behaviour(self):
action = BusinessConnectionHandler(self.callback)
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"
@pytest.fixture(autouse=True)
def _reset(self):
self.test_flag = False
async def callback(self, update, context):
self.test_flag = (
isinstance(context, CallbackContext)
and isinstance(context.bot, Bot)
and isinstance(update, Update)
and isinstance(context.update_queue, asyncio.Queue)
and isinstance(context.job_queue, JobQueue)
and isinstance(context.user_data, dict)
and isinstance(context.bot_data, dict)
and isinstance(
update.business_connection,
BusinessConnection,
)
)
def test_with_user_id(self, business_connection_update):
handler = BusinessConnectionHandler(self.callback, user_id=1)
assert handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, user_id=[1])
assert handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, user_id=2, username="@user_a")
assert handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, user_id=2)
assert not handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, user_id=[2])
assert not handler.check_update(business_connection_update)
def test_with_username(self, business_connection_update):
handler = BusinessConnectionHandler(self.callback, username="user_a")
assert handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, username="@user_a")
assert handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, username=["user_a"])
assert handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, username=["@user_a"])
assert handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, user_id=1, username="@user_b")
assert handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, username="user_b")
assert not handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, username="@user_b")
assert not handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, username=["user_b"])
assert not handler.check_update(business_connection_update)
handler = BusinessConnectionHandler(self.callback, username=["@user_b"])
assert not handler.check_update(business_connection_update)
business_connection_update.business_connection.user._unfreeze()
business_connection_update.business_connection.user.username = None
assert not handler.check_update(business_connection_update)
def test_other_update_types(self, false_update):
handler = BusinessConnectionHandler(self.callback)
assert not handler.check_update(false_update)
assert not handler.check_update(True)
async def test_context(self, app, business_connection_update):
handler = BusinessConnectionHandler(callback=self.callback)
app.add_handler(handler)
async with app:
await app.process_update(business_connection_update)
assert self.test_flag

View file

@ -0,0 +1,170 @@
#!/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 asyncio
import datetime
import pytest
from telegram import (
Bot,
BusinessMessagesDeleted,
CallbackQuery,
Chat,
ChosenInlineResult,
Message,
PreCheckoutQuery,
ShippingQuery,
Update,
User,
)
from telegram._utils.datetime import UTC
from telegram.ext import BusinessMessagesDeletedHandler, CallbackContext, JobQueue
from tests.auxil.slots import mro_slots
message = Message(1, None, Chat(1, ""), from_user=User(1, "", False), text="Text")
params = [
{"message": message},
{"edited_message": message},
{"callback_query": CallbackQuery(1, User(1, "", False), "chat", message=message)},
{"channel_post": message},
{"edited_channel_post": message},
{"chosen_inline_result": ChosenInlineResult("id", User(1, "", False), "")},
{"shipping_query": ShippingQuery("id", User(1, "", False), "", None)},
{"pre_checkout_query": PreCheckoutQuery("id", User(1, "", False), "", 0, "")},
{"callback_query": CallbackQuery(1, User(1, "", False), "chat")},
]
ids = (
"message",
"edited_message",
"callback_query",
"channel_post",
"edited_channel_post",
"chosen_inline_result",
"shipping_query",
"pre_checkout_query",
"callback_query_without_message",
)
@pytest.fixture(scope="class", params=params, ids=ids)
def false_update(request):
return Update(update_id=2, **request.param)
@pytest.fixture(scope="class")
def time():
return datetime.datetime.now(tz=UTC)
@pytest.fixture(scope="class")
def business_messages_deleted(bot):
bmd = BusinessMessagesDeleted(
business_connection_id="1",
chat=Chat(1, Chat.PRIVATE, username="user_a"),
message_ids=[1, 2, 3],
)
bmd.set_bot(bot)
return bmd
@pytest.fixture()
def business_messages_deleted_update(bot, business_messages_deleted):
return Update(0, deleted_business_messages=business_messages_deleted)
class TestBusinessMessagesDeletedHandler:
test_flag = False
def test_slot_behaviour(self):
action = BusinessMessagesDeletedHandler(self.callback)
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"
@pytest.fixture(autouse=True)
def _reset(self):
self.test_flag = False
async def callback(self, update, context):
self.test_flag = (
isinstance(context, CallbackContext)
and isinstance(context.bot, Bot)
and isinstance(update, Update)
and isinstance(context.update_queue, asyncio.Queue)
and isinstance(context.job_queue, JobQueue)
and isinstance(context.chat_data, dict)
and isinstance(context.bot_data, dict)
and isinstance(
update.deleted_business_messages,
BusinessMessagesDeleted,
)
)
def test_with_chat_id(self, business_messages_deleted_update):
handler = BusinessMessagesDeletedHandler(self.callback, chat_id=1)
assert handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, chat_id=[1])
assert handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, chat_id=2, username="@user_a")
assert handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, chat_id=2)
assert not handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, chat_id=[2])
assert not handler.check_update(business_messages_deleted_update)
def test_with_username(self, business_messages_deleted_update):
handler = BusinessMessagesDeletedHandler(self.callback, username="user_a")
assert handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, username="@user_a")
assert handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, username=["user_a"])
assert handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, username=["@user_a"])
assert handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, chat_id=1, username="@user_b")
assert handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, username="user_b")
assert not handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, username="@user_b")
assert not handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, username=["user_b"])
assert not handler.check_update(business_messages_deleted_update)
handler = BusinessMessagesDeletedHandler(self.callback, username=["@user_b"])
assert not handler.check_update(business_messages_deleted_update)
business_messages_deleted_update.deleted_business_messages.chat._unfreeze()
business_messages_deleted_update.deleted_business_messages.chat.username = None
assert not handler.check_update(business_messages_deleted_update)
def test_other_update_types(self, false_update):
handler = BusinessMessagesDeletedHandler(self.callback)
assert not handler.check_update(false_update)
assert not handler.check_update(True)
async def test_context(self, app, business_messages_deleted_update):
handler = BusinessMessagesDeletedHandler(callback=self.callback)
app.add_handler(handler)
async with app:
await app.process_update(business_messages_deleted_update)
assert self.test_flag

View file

@ -32,6 +32,7 @@ from telegram import (
from telegram.error import TelegramError
from telegram.ext import ApplicationBuilder, CallbackContext, Job
from telegram.warnings import PTBUserWarning
from tests.auxil.pytest_classes import make_bot
from tests.auxil.slots import mro_slots
"""
@ -211,8 +212,9 @@ class TestCallbackContext:
finally:
app.bot = bot
async def test_drop_callback_data(self, bot, monkeypatch, chat_id):
app = ApplicationBuilder().token(bot.token).arbitrary_callback_data(True).build()
async def test_drop_callback_data(self, bot, chat_id):
new_bot = make_bot(token=bot.token, arbitrary_callback_data=True)
app = ApplicationBuilder().bot(new_bot).build()
update = Update(
0, message=Message(0, None, Chat(1, "chat"), from_user=User(1, "user", False))

View file

@ -2035,6 +2035,11 @@ class TestFilters:
update.message.is_automatic_forward = True
assert filters.IS_AUTOMATIC_FORWARD.check_update(update)
def test_filters_is_from_offline(self, update):
assert not filters.IS_FROM_OFFLINE.check_update(update)
update.message.is_from_offline = True
assert filters.IS_FROM_OFFLINE.check_update(update)
def test_filters_is_topic_message(self, update):
assert not filters.IS_TOPIC_MESSAGE.check_update(update)
update.message.is_topic_message = True
@ -2343,6 +2348,9 @@ class TestFilters:
assert not filters.UpdateType.EDITED_CHANNEL_POST.check_update(update)
assert not filters.UpdateType.CHANNEL_POSTS.check_update(update)
assert not filters.UpdateType.EDITED.check_update(update)
assert not filters.UpdateType.BUSINESS_MESSAGES.check_update(update)
assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update)
assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update)
def test_update_type_edited_message(self, update):
update.edited_message, update.message = update.message, update.edited_message
@ -2353,6 +2361,9 @@ class TestFilters:
assert not filters.UpdateType.EDITED_CHANNEL_POST.check_update(update)
assert not filters.UpdateType.CHANNEL_POSTS.check_update(update)
assert filters.UpdateType.EDITED.check_update(update)
assert not filters.UpdateType.BUSINESS_MESSAGES.check_update(update)
assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update)
assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update)
def test_update_type_channel_post(self, update):
update.channel_post, update.message = update.message, update.edited_message
@ -2363,6 +2374,9 @@ class TestFilters:
assert not filters.UpdateType.EDITED_CHANNEL_POST.check_update(update)
assert filters.UpdateType.CHANNEL_POSTS.check_update(update)
assert not filters.UpdateType.EDITED.check_update(update)
assert not filters.UpdateType.BUSINESS_MESSAGES.check_update(update)
assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update)
assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update)
def test_update_type_edited_channel_post(self, update):
update.edited_channel_post, update.message = update.message, update.edited_message
@ -2373,6 +2387,35 @@ class TestFilters:
assert filters.UpdateType.EDITED_CHANNEL_POST.check_update(update)
assert filters.UpdateType.CHANNEL_POSTS.check_update(update)
assert filters.UpdateType.EDITED.check_update(update)
assert not filters.UpdateType.BUSINESS_MESSAGES.check_update(update)
assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update)
assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update)
def test_update_type_business_message(self, update):
update.business_message, update.message = update.message, update.edited_message
assert not filters.UpdateType.MESSAGE.check_update(update)
assert not filters.UpdateType.EDITED_MESSAGE.check_update(update)
assert not filters.UpdateType.MESSAGES.check_update(update)
assert not filters.UpdateType.CHANNEL_POST.check_update(update)
assert not filters.UpdateType.EDITED_CHANNEL_POST.check_update(update)
assert not filters.UpdateType.CHANNEL_POSTS.check_update(update)
assert not filters.UpdateType.EDITED.check_update(update)
assert filters.UpdateType.BUSINESS_MESSAGES.check_update(update)
assert filters.UpdateType.BUSINESS_MESSAGE.check_update(update)
assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update)
def test_update_type_edited_business_message(self, update):
update.edited_business_message, update.message = update.message, update.edited_message
assert not filters.UpdateType.MESSAGE.check_update(update)
assert not filters.UpdateType.EDITED_MESSAGE.check_update(update)
assert not filters.UpdateType.MESSAGES.check_update(update)
assert not filters.UpdateType.CHANNEL_POST.check_update(update)
assert not filters.UpdateType.EDITED_CHANNEL_POST.check_update(update)
assert not filters.UpdateType.CHANNEL_POSTS.check_update(update)
assert filters.UpdateType.EDITED.check_update(update)
assert filters.UpdateType.BUSINESS_MESSAGES.check_update(update)
assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update)
assert filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update)
def test_merged_short_circuit_and(self, update, base_class):
update.message.text = "/test"

View file

@ -163,7 +163,7 @@ class TestRequestParameterWithoutRequest:
assert request_parameter.input_files == [input_media.media, input_media.thumbnail]
def test_from_input_inputsticker(self):
input_sticker = InputSticker(data_file("telegram.png").read_bytes(), ["emoji"])
input_sticker = InputSticker(data_file("telegram.png").read_bytes(), ["emoji"], "static")
expected = input_sticker.to_dict()
expected.update({"sticker": input_sticker.sticker.attach_uri})
request_parameter = RequestParameter.from_input("key", input_sticker)

83
tests/test_birthdate.py Normal file
View file

@ -0,0 +1,83 @@
#!/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/].
from datetime import datetime
import pytest
from telegram import Birthdate
from tests.auxil.slots import mro_slots
class TestBirthdateBase:
day = 1
month = 1
year = 2022
@pytest.fixture(scope="module")
def birthdate():
return Birthdate(TestBirthdateBase.day, TestBirthdateBase.month, TestBirthdateBase.year)
class TestBirthdateWithoutRequest(TestBirthdateBase):
def test_slot_behaviour(self, birthdate):
for attr in birthdate.__slots__:
assert getattr(birthdate, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(birthdate)) == len(set(mro_slots(birthdate))), "duplicate slot"
def test_to_dict(self, birthdate):
bd_dict = birthdate.to_dict()
assert isinstance(bd_dict, dict)
assert bd_dict["day"] == self.day
assert bd_dict["month"] == self.month
assert bd_dict["year"] == self.year
def test_de_json(self, bot):
json_dict = {"day": self.day, "month": self.month, "year": self.year}
bd = Birthdate.de_json(json_dict, bot)
assert isinstance(bd, Birthdate)
assert bd.day == self.day
assert bd.month == self.month
assert bd.year == self.year
def test_equality(self):
bd1 = Birthdate(1, 1, 2022)
bd2 = Birthdate(1, 1, 2022)
bd3 = Birthdate(1, 1, 2023)
bd4 = Birthdate(1, 2, 2022)
assert bd1 == bd2
assert hash(bd1) == hash(bd2)
assert bd1 == bd3
assert hash(bd1) == hash(bd3)
assert bd1 != bd4
assert hash(bd1) != hash(bd4)
def test_to_date(self, birthdate):
assert isinstance(birthdate.to_date(), datetime)
assert birthdate.to_date() == datetime(self.year, self.month, self.day)
new_bd = birthdate.to_date(2023)
assert new_bd == datetime(2023, self.month, self.day)
def test_to_date_no_year(self):
bd = Birthdate(1, 1)
with pytest.raises(ValueError, match="The `year` argument is required"):
bd.to_date()

View file

@ -39,6 +39,7 @@ from telegram import (
BotDescription,
BotName,
BotShortDescription,
BusinessConnection,
CallbackQuery,
Chat,
ChatAdministratorRights,
@ -2087,6 +2088,37 @@ class TestBotWithoutRequest:
api_kwargs={"chat_id": 2, "user_id": 32, "until_date": until_timestamp},
)
async def test_business_connection_id_argument(self, bot, monkeypatch):
"""We can't connect to a business acc, so we just test that the correct data is passed.
We also can't test every single method easily, so we just test one. Our linting will catch
any unused args with the others."""
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.parameters.get("business_connection_id") == 42
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_message(2, "text", business_connection_id=42)
async def test_get_business_connection(self, bot, monkeypatch):
bci = "42"
user = User(1, "first", False)
user_chat_id = 1
date = dtm.datetime.utcnow()
can_reply = True
is_enabled = True
bc = BusinessConnection(bci, user, user_chat_id, date, can_reply, is_enabled).to_json()
async def do_request(*args, **kwargs):
data = kwargs.get("request_data")
obj = data.parameters.get("business_connection_id")
if obj == bci:
return 200, f'{{"ok": true, "result": {bc}}}'.encode()
return 400, b'{"ok": false, "result": []}'
monkeypatch.setattr(bot.request, "do_request", do_request)
obj = await bot.get_business_connection(business_connection_id=bci)
assert isinstance(obj, BusinessConnection)
class TestBotWithRequest:
"""
@ -3373,8 +3405,8 @@ class TestBotWithRequest:
assert await bot.unpin_all_chat_messages(super_group_id, read_timeout=10)
# get_sticker_set, upload_sticker_file, create_new_sticker_set, add_sticker_to_set,
# set_sticker_position_in_set, delete_sticker_from_set and get_custom_emoji_stickers
# are tested in the test_sticker module.
# set_sticker_position_in_set, delete_sticker_from_set and get_custom_emoji_stickers,
# replace_sticker_in_set are tested in the test_sticker module.
# get_forum_topic_icon_stickers, edit_forum_topic, general_forum etc...
# are tested in the test_forum module.

412
tests/test_business.py Normal file
View file

@ -0,0 +1,412 @@
#!/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/].
from datetime import datetime
import pytest
from telegram import (
BusinessConnection,
BusinessIntro,
BusinessLocation,
BusinessMessagesDeleted,
BusinessOpeningHours,
BusinessOpeningHoursInterval,
Chat,
Location,
Sticker,
User,
)
from telegram._utils.datetime import UTC, to_timestamp
from tests.auxil.slots import mro_slots
class TestBusinessBase:
id_ = "123"
user = User(123, "test_user", False)
user_chat_id = 123
date = datetime.now(tz=UTC).replace(microsecond=0)
can_reply = True
is_enabled = True
message_ids = (123, 321)
business_connection_id = "123"
chat = Chat(123, "test_chat")
title = "Business Title"
message = "Business description"
sticker = Sticker("sticker_id", "unique_id", 50, 50, True, False, Sticker.REGULAR)
address = "address"
location = Location(-23.691288, 46.788279)
opening_minute = 0
closing_minute = 60
time_zone_name = "Country/City"
opening_hours = [
BusinessOpeningHoursInterval(opening, opening + 60) for opening in (0, 24 * 60)
]
@pytest.fixture(scope="module")
def business_connection():
return BusinessConnection(
TestBusinessBase.id_,
TestBusinessBase.user,
TestBusinessBase.user_chat_id,
TestBusinessBase.date,
TestBusinessBase.can_reply,
TestBusinessBase.is_enabled,
)
@pytest.fixture(scope="module")
def business_messages_deleted():
return BusinessMessagesDeleted(
TestBusinessBase.business_connection_id,
TestBusinessBase.chat,
TestBusinessBase.message_ids,
)
@pytest.fixture(scope="module")
def business_intro():
return BusinessIntro(
TestBusinessBase.title,
TestBusinessBase.message,
TestBusinessBase.sticker,
)
@pytest.fixture(scope="module")
def business_location():
return BusinessLocation(
TestBusinessBase.address,
TestBusinessBase.location,
)
@pytest.fixture(scope="module")
def business_opening_hours_interval():
return BusinessOpeningHoursInterval(
TestBusinessBase.opening_minute,
TestBusinessBase.closing_minute,
)
@pytest.fixture(scope="module")
def business_opening_hours():
return BusinessOpeningHours(
TestBusinessBase.time_zone_name,
TestBusinessBase.opening_hours,
)
class TestBusinessConnectionWithoutRequest(TestBusinessBase):
def test_slots(self, business_connection):
bc = business_connection
for attr in bc.__slots__:
assert getattr(bc, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(bc)) == len(set(mro_slots(bc))), "duplicate slot"
def test_de_json(self):
json_dict = {
"id": self.id_,
"user": self.user.to_dict(),
"user_chat_id": self.user_chat_id,
"date": to_timestamp(self.date),
"can_reply": self.can_reply,
"is_enabled": self.is_enabled,
}
bc = BusinessConnection.de_json(json_dict, None)
assert bc.id == self.id_
assert bc.user == self.user
assert bc.user_chat_id == self.user_chat_id
assert bc.date == self.date
assert bc.can_reply == self.can_reply
assert bc.is_enabled == self.is_enabled
assert bc.api_kwargs == {}
assert isinstance(bc, BusinessConnection)
def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = {
"id": self.id_,
"user": self.user.to_dict(),
"user_chat_id": self.user_chat_id,
"date": to_timestamp(self.date),
"can_reply": self.can_reply,
"is_enabled": self.is_enabled,
}
chat_bot = BusinessConnection.de_json(json_dict, bot)
chat_bot_raw = BusinessConnection.de_json(json_dict, raw_bot)
chat_bot_tz = BusinessConnection.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing tzinfo objects is not reliable
date_offset = chat_bot_tz.date.utcoffset()
date_offset_tz = tz_bot.defaults.tzinfo.utcoffset(chat_bot_tz.date.replace(tzinfo=None))
assert chat_bot.date.tzinfo == UTC
assert chat_bot_raw.date.tzinfo == UTC
assert date_offset_tz == date_offset
def test_to_dict(self, business_connection):
bc_dict = business_connection.to_dict()
assert isinstance(bc_dict, dict)
assert bc_dict["id"] == self.id_
assert bc_dict["user"] == self.user.to_dict()
assert bc_dict["user_chat_id"] == self.user_chat_id
assert bc_dict["date"] == to_timestamp(self.date)
assert bc_dict["can_reply"] == self.can_reply
assert bc_dict["is_enabled"] == self.is_enabled
def test_equality(self):
bc1 = BusinessConnection(
self.id_, self.user, self.user_chat_id, self.date, self.can_reply, self.is_enabled
)
bc2 = BusinessConnection(
self.id_, self.user, self.user_chat_id, self.date, self.can_reply, self.is_enabled
)
bc3 = BusinessConnection(
"321", self.user, self.user_chat_id, self.date, self.can_reply, self.is_enabled
)
assert bc1 == bc2
assert hash(bc1) == hash(bc2)
assert bc1 != bc3
assert hash(bc1) != hash(bc3)
class TestBusinessMessagesDeleted(TestBusinessBase):
def test_slots(self, business_messages_deleted):
bmd = business_messages_deleted
for attr in bmd.__slots__:
assert getattr(bmd, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(bmd)) == len(set(mro_slots(bmd))), "duplicate slot"
def test_to_dict(self, business_messages_deleted):
bmd_dict = business_messages_deleted.to_dict()
assert isinstance(bmd_dict, dict)
assert bmd_dict["message_ids"] == list(self.message_ids)
assert bmd_dict["business_connection_id"] == self.business_connection_id
assert bmd_dict["chat"] == self.chat.to_dict()
def test_de_json(self):
json_dict = {
"business_connection_id": self.business_connection_id,
"chat": self.chat.to_dict(),
"message_ids": self.message_ids,
}
bmd = BusinessMessagesDeleted.de_json(json_dict, None)
assert bmd.business_connection_id == self.business_connection_id
assert bmd.chat == self.chat
assert bmd.message_ids == self.message_ids
assert bmd.api_kwargs == {}
assert isinstance(bmd, BusinessMessagesDeleted)
def test_equality(self):
bmd1 = BusinessMessagesDeleted(self.business_connection_id, self.chat, self.message_ids)
bmd2 = BusinessMessagesDeleted(self.business_connection_id, self.chat, self.message_ids)
bmd3 = BusinessMessagesDeleted("1", Chat(4, "random"), [321, 123])
assert bmd1 == bmd2
assert hash(bmd1) == hash(bmd2)
assert bmd1 != bmd3
assert hash(bmd1) != hash(bmd3)
class TestBusinessIntroWithoutRequest(TestBusinessBase):
def test_slot_behaviour(self, business_intro):
intro = business_intro
for attr in intro.__slots__:
assert getattr(intro, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(intro)) == len(set(mro_slots(intro))), "duplicate slot"
def test_to_dict(self, business_intro):
intro_dict = business_intro.to_dict()
assert isinstance(intro_dict, dict)
assert intro_dict["title"] == self.title
assert intro_dict["message"] == self.message
assert intro_dict["sticker"] == self.sticker.to_dict()
def test_de_json(self):
json_dict = {
"title": self.title,
"message": self.message,
"sticker": self.sticker.to_dict(),
}
intro = BusinessIntro.de_json(json_dict, None)
assert intro.title == self.title
assert intro.message == self.message
assert intro.sticker == self.sticker
assert intro.api_kwargs == {}
assert isinstance(intro, BusinessIntro)
def test_equality(self):
intro1 = BusinessIntro(self.title, self.message, self.sticker)
intro2 = BusinessIntro(self.title, self.message, self.sticker)
intro3 = BusinessIntro("Other Business", self.message, self.sticker)
assert intro1 == intro2
assert hash(intro1) == hash(intro2)
assert intro1 is not intro2
assert intro1 != intro3
assert hash(intro1) != hash(intro3)
class TestBusinessLocationWithoutRequest(TestBusinessBase):
def test_slot_behaviour(self, business_location):
inst = business_location
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_to_dict(self, business_location):
blc_dict = business_location.to_dict()
assert isinstance(blc_dict, dict)
assert blc_dict["address"] == self.address
assert blc_dict["location"] == self.location.to_dict()
def test_de_json(self):
json_dict = {
"address": self.address,
"location": self.location.to_dict(),
}
blc = BusinessLocation.de_json(json_dict, None)
assert blc.address == self.address
assert blc.location == self.location
assert blc.api_kwargs == {}
assert isinstance(blc, BusinessLocation)
def test_equality(self):
blc1 = BusinessLocation(self.address, self.location)
blc2 = BusinessLocation(self.address, self.location)
blc3 = BusinessLocation("Other Address", self.location)
assert blc1 == blc2
assert hash(blc1) == hash(blc2)
assert blc1 is not blc2
assert blc1 != blc3
assert hash(blc1) != hash(blc3)
class TestBusinessOpeningHoursIntervalWithoutRequest(TestBusinessBase):
def test_slot_behaviour(self, business_opening_hours_interval):
inst = business_opening_hours_interval
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_to_dict(self, business_opening_hours_interval):
bohi_dict = business_opening_hours_interval.to_dict()
assert isinstance(bohi_dict, dict)
assert bohi_dict["opening_minute"] == self.opening_minute
assert bohi_dict["closing_minute"] == self.closing_minute
def test_de_json(self):
json_dict = {
"opening_minute": self.opening_minute,
"closing_minute": self.closing_minute,
}
bohi = BusinessOpeningHoursInterval.de_json(json_dict, None)
assert bohi.opening_minute == self.opening_minute
assert bohi.closing_minute == self.closing_minute
assert bohi.api_kwargs == {}
assert isinstance(bohi, BusinessOpeningHoursInterval)
def test_equality(self):
bohi1 = BusinessOpeningHoursInterval(self.opening_minute, self.closing_minute)
bohi2 = BusinessOpeningHoursInterval(self.opening_minute, self.closing_minute)
bohi3 = BusinessOpeningHoursInterval(61, 100)
assert bohi1 == bohi2
assert hash(bohi1) == hash(bohi2)
assert bohi1 is not bohi2
assert bohi1 != bohi3
assert hash(bohi1) != hash(bohi3)
@pytest.mark.parametrize(
("opening_minute", "expected"),
[ # openings per docstring
(8 * 60, (0, 8, 0)),
(24 * 60, (1, 0, 0)),
(6 * 24 * 60, (6, 0, 0)),
],
)
def test_opening_time(self, opening_minute, expected):
bohi = BusinessOpeningHoursInterval(opening_minute, -0)
opening_time = bohi.opening_time
assert opening_time == expected
cached = bohi.opening_time
assert cached is opening_time
@pytest.mark.parametrize(
("closing_minute", "expected"),
[ # closings per docstring
(20 * 60 + 30, (0, 20, 30)),
(2 * 24 * 60 - 1, (1, 23, 59)),
(7 * 24 * 60 - 2, (6, 23, 58)),
],
)
def test_closing_time(self, closing_minute, expected):
bohi = BusinessOpeningHoursInterval(-0, closing_minute)
closing_time = bohi.closing_time
assert closing_time == expected
cached = bohi.closing_time
assert cached is closing_time
class TestBusinessOpeningHoursWithoutRequest(TestBusinessBase):
def test_slot_behaviour(self, business_opening_hours):
inst = business_opening_hours
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_to_dict(self, business_opening_hours):
boh_dict = business_opening_hours.to_dict()
assert isinstance(boh_dict, dict)
assert boh_dict["time_zone_name"] == self.time_zone_name
assert boh_dict["opening_hours"] == [opening.to_dict() for opening in self.opening_hours]
def test_de_json(self):
json_dict = {
"time_zone_name": self.time_zone_name,
"opening_hours": [opening.to_dict() for opening in self.opening_hours],
}
boh = BusinessOpeningHours.de_json(json_dict, None)
assert boh.time_zone_name == self.time_zone_name
assert boh.opening_hours == tuple(self.opening_hours)
assert boh.api_kwargs == {}
assert isinstance(boh, BusinessOpeningHours)
def test_equality(self):
boh1 = BusinessOpeningHours(self.time_zone_name, self.opening_hours)
boh2 = BusinessOpeningHours(self.time_zone_name, self.opening_hours)
boh3 = BusinessOpeningHours("Other/Timezone", self.opening_hours)
assert boh1 == boh2
assert hash(boh1) == hash(boh2)
assert boh1 is not boh2
assert boh1 != boh3
assert hash(boh1) != hash(boh3)

View file

@ -21,7 +21,12 @@ import datetime
import pytest
from telegram import (
Birthdate,
Bot,
BusinessIntro,
BusinessLocation,
BusinessOpeningHours,
BusinessOpeningHoursInterval,
Chat,
ChatLocation,
ChatPermissions,
@ -74,6 +79,11 @@ def chat(bot):
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,
business_intro=TestChatBase.business_intro,
business_location=TestChatBase.business_location,
business_opening_hours=TestChatBase.business_opening_hours,
birthdate=Birthdate(1, 1),
personal_chat=TestChatBase.personal_chat,
)
chat.set_bot(bot)
chat._unfreeze()
@ -113,12 +123,20 @@ class TestChatBase:
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(TestChatBase):
@ -139,6 +157,9 @@ class TestChatWithoutRequest(TestChatBase):
"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,
@ -162,6 +183,8 @@ class TestChatWithoutRequest(TestChatBase):
"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(),
}
chat = Chat.de_json(json_dict, bot)
@ -174,6 +197,9 @@ class TestChatWithoutRequest(TestChatBase):
assert chat.permissions == self.permissions
assert chat.slow_mode_delay == self.slow_mode_delay
assert chat.bio == self.bio
assert chat.business_intro == self.business_intro
assert chat.business_location == self.business_location
assert chat.business_opening_hours == self.business_opening_hours
assert chat.has_protected_content == self.has_protected_content
assert chat.has_visible_history == self.has_visible_history
assert chat.has_private_forwards == self.has_private_forwards
@ -202,6 +228,8 @@ class TestChatWithoutRequest(TestChatBase):
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
assert chat.birthdate == self.birthdate
assert chat.personal_chat == self.personal_chat
def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = {
@ -234,6 +262,9 @@ class TestChatWithoutRequest(TestChatBase):
assert chat_dict["permissions"] == chat.permissions.to_dict()
assert chat_dict["slow_mode_delay"] == chat.slow_mode_delay
assert chat_dict["bio"] == chat.bio
assert chat_dict["business_intro"] == chat.business_intro.to_dict()
assert chat_dict["business_location"] == chat.business_location.to_dict()
assert chat_dict["business_opening_hours"] == chat.business_opening_hours.to_dict()
assert chat_dict["has_private_forwards"] == chat.has_private_forwards
assert chat_dict["has_protected_content"] == chat.has_protected_content
assert chat_dict["has_visible_history"] == chat.has_visible_history
@ -267,6 +298,8 @@ class TestChatWithoutRequest(TestChatBase):
)
assert chat_dict["custom_emoji_sticker_set_name"] == chat.custom_emoji_sticker_set_name
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()
def test_always_tuples_attributes(self):
chat = Chat(

View file

@ -176,6 +176,7 @@ class TestConstantsWithoutRequest:
# attribute is deprecated, no need to add it to MessageType
"user_shared",
"via_bot",
"is_from_offline",
}
@pytest.mark.parametrize(

View file

@ -50,6 +50,7 @@ from telegram import (
PollOption,
ProximityAlertTriggered,
ReplyParameters,
SharedUser,
Sticker,
Story,
SuccessfulPayment,
@ -89,6 +90,7 @@ def message(bot):
date=TestMessageBase.date,
chat=copy(TestMessageBase.chat),
from_user=copy(TestMessageBase.from_user),
business_connection_id="123456789",
)
message.set_bot(bot)
message._unfreeze()
@ -218,7 +220,7 @@ def message(bot):
},
{"web_app_data": WebAppData("some_data", "some_button_text")},
{"message_thread_id": 123},
{"users_shared": UsersShared(1, [2, 3])},
{"users_shared": UsersShared(1, users=[SharedUser(2, "user2"), SharedUser(3, "user3")])},
{"chat_shared": ChatShared(3, 4)},
{
"giveaway": Giveaway(
@ -263,6 +265,9 @@ def message(bot):
{"reply_to_story": Story(Chat(1, Chat.PRIVATE), 0)},
{"boost_added": ChatBoostAdded(100)},
{"sender_boost_count": 1},
{"is_from_offline": True},
{"sender_business_bot": User(1, "BusinessBot", True)},
{"business_connection_id": "123456789"},
],
ids=[
"reply",
@ -328,6 +333,9 @@ def message(bot):
"reply_to_story",
"boost_added",
"sender_boost_count",
"sender_business_bot",
"business_connection_id",
"is_from_offline",
],
)
def message_params(bot, request):
@ -1386,7 +1394,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_text,
Bot.send_message,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1394,6 +1402,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_message",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_text, message.get_bot())
@ -1424,7 +1433,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_markdown,
Bot.send_message,
["chat_id", "parse_mode", "reply_to_message_id"],
["chat_id", "parse_mode", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1432,6 +1441,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_message",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_text, message.get_bot())
@ -1466,7 +1476,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_markdown_v2,
Bot.send_message,
["chat_id", "parse_mode", "reply_to_message_id"],
["chat_id", "parse_mode", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1474,6 +1484,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_message",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_text, message.get_bot())
@ -1513,7 +1524,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_html,
Bot.send_message,
["chat_id", "parse_mode", "reply_to_message_id"],
["chat_id", "parse_mode", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1521,6 +1532,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_message",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_text, message.get_bot())
@ -1546,7 +1558,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_media_group,
Bot.send_media_group,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1554,6 +1566,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_media_group",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_media_group, message.get_bot())
@ -1584,7 +1597,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_photo,
Bot.send_photo,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1592,6 +1605,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_photo",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_photo, message.get_bot())
@ -1614,7 +1628,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_audio,
Bot.send_audio,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1622,6 +1636,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_audio",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_audio, message.get_bot())
@ -1644,7 +1659,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_document,
Bot.send_document,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1652,6 +1667,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_document",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_document, message.get_bot())
@ -1674,7 +1690,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_animation,
Bot.send_animation,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1682,6 +1698,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_animation",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_animation, message.get_bot())
@ -1704,7 +1721,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_sticker,
Bot.send_sticker,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1712,6 +1729,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_sticker",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_sticker, message.get_bot())
@ -1734,7 +1752,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_video,
Bot.send_video,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1742,6 +1760,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_video",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_video, message.get_bot())
@ -1764,7 +1783,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_video_note,
Bot.send_video_note,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1772,6 +1791,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_video_note",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_video_note, message.get_bot())
@ -1794,7 +1814,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_voice,
Bot.send_voice,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1802,6 +1822,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_voice",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_voice, message.get_bot())
@ -1824,7 +1845,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_location,
Bot.send_location,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1832,6 +1853,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_location",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_location, message.get_bot())
@ -1854,7 +1876,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_venue,
Bot.send_venue,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1862,6 +1884,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_venue",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_venue, message.get_bot())
@ -1884,7 +1907,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_contact,
Bot.send_contact,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -1892,6 +1915,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_contact",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_contact, message.get_bot())
@ -1915,11 +1939,15 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_poll,
Bot.send_poll,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
message.reply_poll, message.get_bot(), "send_poll", skip_params=["reply_to_message_id"]
message.reply_poll,
message.get_bot(),
"send_poll",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_poll, message.get_bot())
@ -1942,11 +1970,15 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_dice,
Bot.send_dice,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
message.reply_dice, message.get_bot(), "send_dice", skip_params=["reply_to_message_id"]
message.reply_dice,
message.get_bot(),
"send_dice",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_dice, message.get_bot())
@ -1971,10 +2003,16 @@ class TestMessageWithoutRequest(TestMessageBase):
return id_ and action
assert check_shortcut_signature(
Message.reply_chat_action, Bot.send_chat_action, ["chat_id", "reply_to_message_id"], []
Message.reply_chat_action,
Bot.send_chat_action,
["chat_id", "reply_to_message_id", "business_connection_id"],
[],
)
assert await check_shortcut_call(
message.reply_chat_action, message.get_bot(), "send_chat_action"
message.reply_chat_action,
message.get_bot(),
"send_chat_action",
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_chat_action, message.get_bot())
@ -1998,11 +2036,15 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_game,
Bot.send_game,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
message.reply_game, message.get_bot(), "send_game", skip_params=["reply_to_message_id"]
message.reply_game,
message.get_bot(),
"send_game",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_game, message.get_bot())
@ -2034,7 +2076,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_invoice,
Bot.send_invoice,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(
@ -2042,6 +2084,7 @@ class TestMessageWithoutRequest(TestMessageBase):
message.get_bot(),
"send_invoice",
skip_params=["reply_to_message_id"],
shortcut_kwargs=["business_connection_id"],
)
assert await check_defaults_handling(message.reply_invoice, message.get_bot())
@ -2159,7 +2202,7 @@ class TestMessageWithoutRequest(TestMessageBase):
assert check_shortcut_signature(
Message.reply_copy,
Bot.copy_message,
["chat_id", "reply_to_message_id"],
["chat_id", "reply_to_message_id", "business_connection_id"],
["quote", "do_quote", "reply_to_message_id"],
)
assert await check_shortcut_call(message.copy, message.get_bot(), "copy_message")

View file

@ -166,7 +166,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]] = {
"create_new_sticker_set": {"sticker_format"}, # removed by bot api 7.2
"StickerSet": {"is_animated", "is_video"}, # removed by bot api 7.2
"UsersShared": {"user_ids", "users"}, # removed/added by bot api 7.2
}
def backwards_compat_kwargs(object_name: str) -> set[str]:

View file

@ -19,19 +19,20 @@
import pytest
from telegram import ChatShared, UsersShared
from telegram import ChatShared, PhotoSize, SharedUser, UsersShared
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
def users_shared():
return UsersShared(TestUsersSharedBase.request_id, TestUsersSharedBase.user_ids)
return UsersShared(TestUsersSharedBase.request_id, users=TestUsersSharedBase.users)
class TestUsersSharedBase:
request_id = 789
user_id = 101112
user_ids = (user_id, 101113)
user_ids = (101112, 101113)
users = (SharedUser(101112, "user1"), SharedUser(101113, "user2"))
class TestUsersSharedWithoutRequest(TestUsersSharedBase):
@ -45,24 +46,43 @@ class TestUsersSharedWithoutRequest(TestUsersSharedBase):
assert isinstance(users_shared_dict, dict)
assert users_shared_dict["request_id"] == self.request_id
assert users_shared_dict["user_ids"] == list(self.user_ids)
assert users_shared_dict["users"] == [user.to_dict() for user in self.users]
def test_de_json(self, bot):
json_dict = {
"request_id": self.request_id,
"users": [user.to_dict() for user in self.users],
"user_ids": self.user_ids,
}
users_shared = UsersShared.de_json(json_dict, bot)
assert users_shared.api_kwargs == {}
assert users_shared.api_kwargs == {"user_ids": self.user_ids}
assert users_shared.request_id == self.request_id
assert users_shared.users == self.users
assert users_shared.user_ids == tuple(self.user_ids)
assert UsersShared.de_json({}, bot) is None
def test_users_is_required_argument(self):
with pytest.raises(TypeError, match="`users` is a required argument"):
UsersShared(self.request_id, user_ids=self.user_ids)
def test_user_ids_deprecation_warning(self):
with pytest.warns(
PTBDeprecationWarning, match="'user_ids' was renamed to 'users' in Bot API 7.2"
):
users_shared = UsersShared(self.request_id, user_ids=self.user_ids, users=self.users)
with pytest.warns(
PTBDeprecationWarning, match="renamed the attribute 'user_ids' to 'users'"
):
users_shared.user_ids
def test_equality(self):
a = UsersShared(self.request_id, self.user_ids)
b = UsersShared(self.request_id, self.user_ids)
c = UsersShared(1, self.user_ids)
d = UsersShared(self.request_id, [1, 2])
a = UsersShared(self.request_id, users=self.users)
b = UsersShared(self.request_id, users=self.users)
c = UsersShared(1, users=self.users)
d = UsersShared(self.request_id, users=(SharedUser(1, "user1"), SharedUser(1, "user2")))
e = PhotoSize("file_id", "1", 1, 1)
assert a == b
assert hash(a) == hash(b)
@ -74,6 +94,9 @@ class TestUsersSharedWithoutRequest(TestUsersSharedBase):
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
@pytest.fixture(scope="class")
def chat_shared():
@ -112,11 +135,109 @@ class TestChatSharedWithoutRequest(TestChatSharedBase):
assert chat_shared.request_id == self.request_id
assert chat_shared.chat_id == self.chat_id
def test_equality(self):
def test_equality(self, users_shared):
a = ChatShared(self.request_id, self.chat_id)
b = ChatShared(self.request_id, self.chat_id)
c = ChatShared(1, self.chat_id)
d = ChatShared(self.request_id, 1)
e = users_shared
assert a == b
assert hash(a) == hash(b)
assert a is not 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="class")
def shared_user():
return SharedUser(
TestSharedUserBase.user_id,
TestSharedUserBase.first_name,
last_name=TestSharedUserBase.last_name,
username=TestSharedUserBase.username,
photo=TestSharedUserBase.photo,
)
class TestSharedUserBase:
user_id = 101112
first_name = "first"
last_name = "last"
username = "user"
photo = (
PhotoSize(file_id="file_id", width=1, height=1, file_unique_id="1"),
PhotoSize(file_id="file_id", width=2, height=2, file_unique_id="2"),
)
class TestSharedUserWithoutRequest(TestSharedUserBase):
def test_slot_behaviour(self, shared_user):
for attr in shared_user.__slots__:
assert getattr(shared_user, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(shared_user)) == len(set(mro_slots(shared_user))), "duplicate slot"
def test_to_dict(self, shared_user):
shared_user_dict = shared_user.to_dict()
assert isinstance(shared_user_dict, dict)
assert shared_user_dict["user_id"] == self.user_id
assert shared_user_dict["first_name"] == self.first_name
assert shared_user_dict["last_name"] == self.last_name
assert shared_user_dict["username"] == self.username
assert shared_user_dict["photo"] == [photo.to_dict() for photo in self.photo]
def test_de_json_required(self, bot):
json_dict = {
"user_id": self.user_id,
"first_name": self.first_name,
}
shared_user = SharedUser.de_json(json_dict, bot)
assert shared_user.api_kwargs == {}
assert shared_user.user_id == self.user_id
assert shared_user.first_name == self.first_name
assert shared_user.last_name is None
assert shared_user.username is None
assert shared_user.photo == ()
def test_de_json_all(self, bot):
json_dict = {
"user_id": self.user_id,
"first_name": self.first_name,
"last_name": self.last_name,
"username": self.username,
"photo": [photo.to_dict() for photo in self.photo],
}
shared_user = SharedUser.de_json(json_dict, bot)
assert shared_user.api_kwargs == {}
assert shared_user.user_id == self.user_id
assert shared_user.first_name == self.first_name
assert shared_user.last_name == self.last_name
assert shared_user.username == self.username
assert shared_user.photo == self.photo
assert SharedUser.de_json({}, bot) is None
def test_equality(self, chat_shared):
a = SharedUser(
self.user_id,
self.first_name,
last_name=self.last_name,
username=self.username,
photo=self.photo,
)
b = SharedUser(self.user_id, "other_firs_name")
c = SharedUser(self.user_id + 1, self.first_name)
d = chat_shared
assert a == b
assert hash(a) == hash(b)

View file

@ -23,6 +23,8 @@ from datetime import datetime
import pytest
from telegram import (
BusinessConnection,
BusinessMessagesDeleted,
CallbackQuery,
Chat,
ChatBoost,
@ -119,6 +121,28 @@ message_reaction_count = MessageReactionCountUpdated(
reactions=(ReactionCount(ReactionTypeEmoji("👍"), 1),),
)
business_connection = BusinessConnection(
"1",
User(1, "name", False),
1,
from_timestamp(int(time.time())),
True,
True,
)
deleted_business_messages = BusinessMessagesDeleted(
"1",
Chat(1, ""),
(1, 2),
)
business_message = Message(
1,
datetime.utcnow(),
Chat(1, ""),
User(1, "", False),
)
params = [
{"message": message},
@ -150,6 +174,10 @@ params = [
{"removed_chat_boost": removed_chat_boost},
{"message_reaction": message_reaction},
{"message_reaction_count": message_reaction_count},
{"business_connection": business_connection},
{"deleted_business_messages": deleted_business_messages},
{"business_message": business_message},
{"edited_business_message": business_message},
# Must be last to conform with `ids` below!
{"callback_query": CallbackQuery(1, User(1, "", False), "chat")},
]
@ -173,6 +201,10 @@ all_types = (
"removed_chat_boost",
"message_reaction",
"message_reaction_count",
"business_connection",
"deleted_business_messages",
"business_message",
"edited_business_message",
)
ids = (*all_types, "callback_query_without_message")
@ -257,6 +289,7 @@ class TestUpdateWithoutRequest(TestUpdateBase):
or update.pre_checkout_query is not None
or update.poll is not None
or update.poll_answer is not None
or update.business_connection is not None
):
assert chat.id == 1
else:
@ -272,6 +305,7 @@ class TestUpdateWithoutRequest(TestUpdateBase):
or update.chat_boost is not None
or update.removed_chat_boost is not None
or update.message_reaction_count is not None
or update.deleted_business_messages is not None
):
assert user.id == 1
else:
@ -297,6 +331,7 @@ class TestUpdateWithoutRequest(TestUpdateBase):
or update.chat_boost is not None
or update.removed_chat_boost is not None
or update.message_reaction_count is not None
or update.deleted_business_messages is not None
):
if update.channel_post or update.edited_channel_post:
assert isinstance(sender, Chat)
@ -329,6 +364,7 @@ class TestUpdateWithoutRequest(TestUpdateBase):
or update.chat_boost is not None
or update.removed_chat_boost is not None
or update.message_reaction_count is not None
or update.deleted_business_messages is not None
):
if (
update.message
@ -365,6 +401,8 @@ class TestUpdateWithoutRequest(TestUpdateBase):
or update.removed_chat_boost is not None
or update.message_reaction is not None
or update.message_reaction_count is not None
or update.deleted_business_messages is not None
or update.business_connection is not None
):
assert eff_message.message_id == message.message_id
else:

View file

@ -42,6 +42,7 @@ def json_dict():
"supports_inline_queries": TestUserBase.supports_inline_queries,
"is_premium": TestUserBase.is_premium,
"added_to_attachment_menu": TestUserBase.added_to_attachment_menu,
"can_connect_to_business": TestUserBase.can_connect_to_business,
}
@ -59,6 +60,7 @@ def user(bot):
supports_inline_queries=TestUserBase.supports_inline_queries,
is_premium=TestUserBase.is_premium,
added_to_attachment_menu=TestUserBase.added_to_attachment_menu,
can_connect_to_business=TestUserBase.can_connect_to_business,
)
user.set_bot(bot)
user._unfreeze()
@ -77,6 +79,7 @@ class TestUserBase:
supports_inline_queries = False
is_premium = True
added_to_attachment_menu = False
can_connect_to_business = True
class TestUserWithoutRequest(TestUserBase):
@ -100,6 +103,7 @@ class TestUserWithoutRequest(TestUserBase):
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
assert user.can_connect_to_business == self.can_connect_to_business
def test_to_dict(self, user):
user_dict = user.to_dict()
@ -116,6 +120,7 @@ class TestUserWithoutRequest(TestUserBase):
assert user_dict["supports_inline_queries"] == user.supports_inline_queries
assert user_dict["is_premium"] == user.is_premium
assert user_dict["added_to_attachment_menu"] == user.added_to_attachment_menu
assert user_dict["can_connect_to_business"] == user.can_connect_to_business
def test_equality(self):
a = User(self.id_, self.first_name, self.is_bot, self.last_name)