* Feat: New invite links

* Fix: doc strings

Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>

* new dice, new admin privilege, revoke_messages, update and fix some docs

* add missing param to shortcut

* Add ChatMemberUpdated

* Add voicechat related objects

Signed-off-by: starry69 <starry369126@outlook.com>

* add versionadd tags

Signed-off-by: starry69 <starry369126@outlook.com>

* Fix filter tests

* Update tg.Update

* ChatMemberHandler

* Add versioning directives

* add can_manage_voice_chats attr and fix docs

Signed-off-by: starry69 <starry369126@outlook.com>

* fix chat shortcut

Signed-off-by: starry69 <starry369126@outlook.com>

* address review

* MADTC

* Chat.message_auto_delete_time

* Some doc fixes

* address review

Signed-off-by: starry69 <starry369126@outlook.com>

* welp

Signed-off-by: starry69 <starry369126@outlook.com>

* Add voicechat related filters

Signed-off-by: starry69 <starry369126@outlook.com>

* Fix: Addressing review

change place of version adding, added obj:True as doc string, changing how member limit is initiated

* feat: adding chat shortcuts for invite links

* fix: changing equality of chatinviteobjects

* Non-test comments

* Some test fixes

* A bit more tests

* Bump API version in both readmes

* Increase coverage

* Add Bot API Version in telegram.constants (#2429)

* add bot api version in constants

Signed-off-by: starry69 <starry369126@outlook.com>

* addressing review

Signed-off-by: starry69 <starry369126@outlook.com>

* add versioning directive

Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>

* pre-commit & coverage

Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de>
Co-authored-by: Harshil <ilovebhagwan@gmail.com>
Co-authored-by: starry69 <starry369126@outlook.com>
This commit is contained in:
Poolitzer 2021-03-14 16:41:35 +01:00 committed by GitHub
parent 3a9a0ab96d
commit ac02bce109
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 2109 additions and 64 deletions

View file

@ -27,4 +27,4 @@ Hey! You're PRing? Cool! Please have a look at the below checklist. It's here to
- [ ] Added new handlers for new update types - [ ] Added new handlers for new update types
- [ ] Added new filters for new message (sub)types - [ ] Added new filters for new message (sub)types
- [ ] Added or updated documentation for the changed class(es) and/or method(s) - [ ] Added or updated documentation for the changed class(es) and/or method(s)
- [ ] Updated the Bot API version number in all places in `README.rst` and `README_RAW.rst`, including the badge - [ ] Updated the Bot API version number in all places: `README.rst` and `README_RAW.rst` (including the badge), as well as `telegram.constants.BOT_API_VERSION`

View file

@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: https://pypi.org/project/python-telegram-bot/ :target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions :alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-5.0-blue?logo=telegram .. image:: https://img.shields.io/badge/Bot%20API-5.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog :target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions :alt: Supported Bot API versions
@ -111,7 +111,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support Telegram API support
==================== ====================
All types and methods of the Telegram Bot API **5.0** are supported. All types and methods of the Telegram Bot API **5.1** are supported.
========== ==========
Installing Installing

View file

@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: https://pypi.org/project/python-telegram-bot-raw/ :target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions :alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-5.0-blue?logo=telegram .. image:: https://img.shields.io/badge/Bot%20API-5.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog :target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions :alt: Supported Bot API versions
@ -105,7 +105,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support Telegram API support
==================== ====================
All types and methods of the Telegram Bot API **5.0** are supported. All types and methods of the Telegram Bot API **5.1** are supported.
========== ==========
Installing Installing

View file

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

View file

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

View file

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

View file

@ -21,6 +21,7 @@ Handlers
telegram.ext.handler telegram.ext.handler
telegram.ext.callbackqueryhandler telegram.ext.callbackqueryhandler
telegram.ext.choseninlineresulthandler telegram.ext.choseninlineresulthandler
telegram.ext.chatmemberhandler
telegram.ext.commandhandler telegram.ext.commandhandler
telegram.ext.conversationhandler telegram.ext.conversationhandler
telegram.ext.inlinequeryhandler telegram.ext.inlinequeryhandler

View file

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

View file

@ -13,8 +13,10 @@ telegram package
telegram.callbackquery telegram.callbackquery
telegram.chat telegram.chat
telegram.chataction telegram.chataction
telegram.chatinvitelink
telegram.chatlocation telegram.chatlocation
telegram.chatmember telegram.chatmember
telegram.chatmemberupdated
telegram.chatpermissions telegram.chatpermissions
telegram.chatphoto telegram.chatphoto
telegram.constants telegram.constants
@ -38,6 +40,7 @@ telegram package
telegram.location telegram.location
telegram.loginurl telegram.loginurl
telegram.message telegram.message
telegram.messageautodeletetimerchanged
telegram.messageid telegram.messageid
telegram.messageentity telegram.messageentity
telegram.parsemode telegram.parsemode
@ -57,6 +60,9 @@ telegram package
telegram.video telegram.video
telegram.videonote telegram.videonote
telegram.voice telegram.voice
telegram.voicechatstarted
telegram.voicechatended
telegram.voicechatparticipantsinvited
telegram.webhookinfo telegram.webhookinfo
Stickers Stickers

View file

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

View file

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

View file

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

View file

@ -24,7 +24,9 @@ from .user import User
from .files.chatphoto import ChatPhoto from .files.chatphoto import ChatPhoto
from .chat import Chat from .chat import Chat
from .chatlocation import ChatLocation from .chatlocation import ChatLocation
from .chatinvitelink import ChatInviteLink
from .chatmember import ChatMember from .chatmember import ChatMember
from .chatmemberupdated import ChatMemberUpdated
from .chatpermissions import ChatPermissions from .chatpermissions import ChatPermissions
from .files.photosize import PhotoSize from .files.photosize import PhotoSize
from .files.audio import Audio from .files.audio import Audio
@ -54,6 +56,7 @@ from .messageentity import MessageEntity
from .messageid import MessageId from .messageid import MessageId
from .games.game import Game from .games.game import Game
from .poll import Poll, PollOption, PollAnswer from .poll import Poll, PollOption, PollAnswer
from .voicechat import VoiceChatStarted, VoiceChatEnded, VoiceChatParticipantsInvited
from .loginurl import LoginUrl from .loginurl import LoginUrl
from .proximityalerttriggered import ProximityAlertTriggered from .proximityalerttriggered import ProximityAlertTriggered
from .games.callbackgame import CallbackGame from .games.callbackgame import CallbackGame
@ -68,6 +71,7 @@ from .passport.encryptedpassportelement import EncryptedPassportElement
from .passport.passportdata import PassportData from .passport.passportdata import PassportData
from .inline.inlinekeyboardbutton import InlineKeyboardButton from .inline.inlinekeyboardbutton import InlineKeyboardButton
from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from .messageautodeletetimerchanged import MessageAutoDeleteTimerChanged
from .message import Message from .message import Message
from .callbackquery import CallbackQuery from .callbackquery import CallbackQuery
from .choseninlineresult import ChosenInlineResult from .choseninlineresult import ChosenInlineResult
@ -144,7 +148,7 @@ from .passport.credentials import (
TelegramDecryptionError, TelegramDecryptionError,
) )
from .bot import Bot from .bot import Bot
from .version import __version__ # noqa: F401 from .version import __version__, bot_api_version # noqa: F401
__author__ = 'devs@python-telegram-bot.org' __author__ = 'devs@python-telegram-bot.org'
@ -157,8 +161,10 @@ __all__ = ( # Keep this alphabetically ordered
'CallbackQuery', 'CallbackQuery',
'Chat', 'Chat',
'ChatAction', 'ChatAction',
'ChatInviteLink',
'ChatLocation', 'ChatLocation',
'ChatMember', 'ChatMember',
'ChatMemberUpdated',
'ChatPermissions', 'ChatPermissions',
'ChatPhoto', 'ChatPhoto',
'ChosenInlineResult', 'ChosenInlineResult',
@ -226,6 +232,7 @@ __all__ = ( # Keep this alphabetically ordered
'MAX_MESSAGE_LENGTH', 'MAX_MESSAGE_LENGTH',
'MaskPosition', 'MaskPosition',
'Message', 'Message',
'MessageAutoDeleteTimerChanged',
'MessageEntity', 'MessageEntity',
'MessageId', 'MessageId',
'OrderInfo', 'OrderInfo',
@ -272,5 +279,8 @@ __all__ = ( # Keep this alphabetically ordered
'Video', 'Video',
'VideoNote', 'VideoNote',
'Voice', 'Voice',
'VoiceChatStarted',
'VoiceChatEnded',
'VoiceChatParticipantsInvited',
'WebhookInfo', 'WebhookInfo',
) )

View file

@ -24,6 +24,7 @@ from typing import Optional
import certifi import certifi
from . import __version__ as telegram_ver from . import __version__ as telegram_ver
from .constants import BOT_API_VERSION
def _git_revision() -> Optional[str]: def _git_revision() -> Optional[str]:
@ -39,6 +40,7 @@ def _git_revision() -> Optional[str]:
def print_ver_info() -> None: def print_ver_info() -> None:
git_revision = _git_revision() git_revision = _git_revision()
print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else '')) print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else ''))
print(f'Bot API {BOT_API_VERSION}')
print(f'certifi {certifi.__version__}') # type: ignore[attr-defined] print(f'certifi {certifi.__version__}') # type: ignore[attr-defined]
sys_version = sys.version.replace('\n', ' ') sys_version = sys.version.replace('\n', ' ')
print(f'Python {sys_version}') print(f'Python {sys_version}')

View file

@ -46,15 +46,13 @@ class TelegramObject:
@staticmethod @staticmethod
def parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]: def parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
if not data: return None if data is None else data.copy()
return None
return data.copy()
@classmethod @classmethod
def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]: def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]:
data = cls.parse_data(data) data = cls.parse_data(data)
if not data: if data is None:
return None return None
if cls == TelegramObject: if cls == TelegramObject:

View file

@ -85,6 +85,7 @@ from telegram import (
Voice, Voice,
WebhookInfo, WebhookInfo,
InlineKeyboardMarkup, InlineKeyboardMarkup,
ChatInviteLink,
) )
from telegram.constants import MAX_INLINE_QUERY_RESULTS from telegram.constants import MAX_INLINE_QUERY_RESULTS
from telegram.error import InvalidToken, TelegramError from telegram.error import InvalidToken, TelegramError
@ -297,7 +298,7 @@ class Bot(TelegramObject):
if result is True: if result is True:
return result return result
return Message.de_json(result, self) # type: ignore[arg-type,return-value] return Message.de_json(result, self) # type: ignore[return-value, arg-type]
@property @property
def request(self) -> Request: def request(self) -> Request:
@ -406,7 +407,7 @@ class Bot(TelegramObject):
""" """
result = self._post('getMe', timeout=timeout, api_kwargs=api_kwargs) result = self._post('getMe', timeout=timeout, api_kwargs=api_kwargs)
self._bot = User.de_json(result, self) # type: ignore self._bot = User.de_json(result, self) # type: ignore[return-value, arg-type]
return self._bot # type: ignore[return-value] return self._bot # type: ignore[return-value]
@ -2185,7 +2186,7 @@ class Bot(TelegramObject):
result = self._post('getUserProfilePhotos', data, timeout=timeout, api_kwargs=api_kwargs) result = self._post('getUserProfilePhotos', data, timeout=timeout, api_kwargs=api_kwargs)
return UserProfilePhotos.de_json(result, self) # type: ignore return UserProfilePhotos.de_json(result, self) # type: ignore[return-value, arg-type]
@log @log
def get_file( def get_file(
@ -2245,7 +2246,7 @@ class Bot(TelegramObject):
self.base_file_url, result['file_path'] # type: ignore[index] self.base_file_url, result['file_path'] # type: ignore[index]
) )
return File.de_json(result, self) # type: ignore return File.de_json(result, self) # type: ignore[return-value, arg-type]
@log @log
def kick_chat_member( def kick_chat_member(
@ -2255,25 +2256,33 @@ class Bot(TelegramObject):
timeout: ODVInput[float] = DEFAULT_NONE, timeout: ODVInput[float] = DEFAULT_NONE,
until_date: Union[int, datetime] = None, until_date: Union[int, datetime] = None,
api_kwargs: JSONDict = None, api_kwargs: JSONDict = None,
revoke_messages: bool = None,
) -> bool: ) -> bool:
""" """
Use this method to kick a user from a group or a supergroup or a channel. In the case of Use this method to kick a user from a group, supergroup or a channel. In the case of
supergroups and channels, the user will not be able to return to the group on their own supergroups and channels, the user will not be able to return to the group on their own
using invite links, etc., unless unbanned first. The bot must be an administrator in the using invite links, etc., unless unbanned first. The bot must be an administrator in the
group for this to work. chat for this to work and must have the appropriate admin rights.
Args: Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target group or username
of the target channel (in the format @channelusername). of the target supergroup or channel (in the format @channelusername).
user_id (:obj:`int`): Unique identifier of the target user. user_id (:obj:`int`): Unique identifier of the target user.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of the read timeout from the server (instead of the one specified during creation of
the connection pool). the connection pool).
until_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the user will until_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the user will
be unbanned, unix time. If user is banned for more than 366 days or less than 30 be unbanned, unix time. If user is banned for more than 366 days or less than 30
seconds from the current time they are considered to be banned forever. seconds from the current time they are considered to be banned forever. Applied
for supergroups and channels only.
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
bot will be used. bot will be used.
revoke_messages (:obj:`bool`, optional): Pass :obj:`True` to delete all messages from
the chat for the user that is being removed. If :obj:`False`, the user will be able
to see messages in the group that were sent before the user was removed.
Always :obj:`True` for supergroups and channels.
.. versionadded:: 13.4
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API. Telegram API.
@ -2293,6 +2302,9 @@ class Bot(TelegramObject):
) )
data['until_date'] = until_date data['until_date'] = until_date
if revoke_messages is not None:
data['revoke_messages'] = revoke_messages
result = self._post('kickChatMember', data, timeout=timeout, api_kwargs=api_kwargs) result = self._post('kickChatMember', data, timeout=timeout, api_kwargs=api_kwargs)
return result # type: ignore[return-value] return result # type: ignore[return-value]
@ -2711,10 +2723,11 @@ class Bot(TelegramObject):
updates you want your bot to receive. For example, specify ["message", updates you want your bot to receive. For example, specify ["message",
"edited_channel_post", "callback_query"] to only receive updates of these types. "edited_channel_post", "callback_query"] to only receive updates of these types.
See :class:`telegram.Update` for a complete list of available update types. See :class:`telegram.Update` for a complete list of available update types.
Specify an empty list to receive all updates regardless of type (default). If not Specify an empty list to receive all updates except
specified, the previous setting will be used. Please note that this parameter :attr:`telegram.Update.chat_member` (default). If not specified, the previous
doesn't affect updates created before the call to the get_updates, so unwanted setting will be used. Please note that this parameter doesn't affect updates
updates may be received for a short period of time. created before the call to the get_updates, so unwanted updates may be received for
a short period of time.
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API. Telegram API.
@ -2799,10 +2812,11 @@ class Bot(TelegramObject):
updates you want your bot to receive. For example, specify ["message", updates you want your bot to receive. For example, specify ["message",
"edited_channel_post", "callback_query"] to only receive updates of these types. "edited_channel_post", "callback_query"] to only receive updates of these types.
See :class:`telegram.Update` for a complete list of available update types. See :class:`telegram.Update` for a complete list of available update types.
Specify an empty list to receive all updates regardless of type (default). If not Specify an empty list to receive all updates except
specified, the previous setting will be used. Please note that this parameter :attr:`telegram.Update.chat_member` (default). If not specified, the previous
doesn't affect updates created before the call to the set_webhook, so unwanted setting will be used. Please note that this parameter doesn't affect updates
updates may be received for a short period of time. created before the call to the set_webhook, so unwanted updates may be received for
a short period of time.
drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending
updates. updates.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
@ -2948,7 +2962,7 @@ class Bot(TelegramObject):
result = self._post('getChat', data, timeout=timeout, api_kwargs=api_kwargs) result = self._post('getChat', data, timeout=timeout, api_kwargs=api_kwargs)
return Chat.de_json(result, self) # type: ignore return Chat.de_json(result, self) # type: ignore[return-value, arg-type]
@log @log
def get_chat_administrators( def get_chat_administrators(
@ -3047,7 +3061,7 @@ class Bot(TelegramObject):
result = self._post('getChatMember', data, timeout=timeout, api_kwargs=api_kwargs) result = self._post('getChatMember', data, timeout=timeout, api_kwargs=api_kwargs)
return ChatMember.de_json(result, self) # type: ignore return ChatMember.de_json(result, self) # type: ignore[return-value, arg-type]
@log @log
def set_chat_sticker_set( def set_chat_sticker_set(
@ -3132,7 +3146,7 @@ class Bot(TelegramObject):
""" """
result = self._post('getWebhookInfo', None, timeout=timeout, api_kwargs=api_kwargs) result = self._post('getWebhookInfo', None, timeout=timeout, api_kwargs=api_kwargs)
return WebhookInfo.de_json(result, self) # type: ignore return WebhookInfo.de_json(result, self) # type: ignore[return-value, arg-type]
@log @log
def set_game_score( def set_game_score(
@ -3588,6 +3602,8 @@ class Bot(TelegramObject):
timeout: ODVInput[float] = DEFAULT_NONE, timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None, api_kwargs: JSONDict = None,
is_anonymous: bool = None, is_anonymous: bool = None,
can_manage_chat: bool = None,
can_manage_voice_chats: bool = None,
) -> bool: ) -> bool:
""" """
Use this method to promote or demote a user in a supergroup or a channel. The bot must be Use this method to promote or demote a user in a supergroup or a channel. The bot must be
@ -3596,16 +3612,28 @@ class Bot(TelegramObject):
Args: Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target supergroup (in the format @supergroupusername). of the target channel (in the format @channelusername).
user_id (:obj:`int`): Unique identifier of the target user. user_id (:obj:`int`): Unique identifier of the target user.
is_anonymous (:obj:`bool`, optional): Pass :obj:`True`, if the administrator's presence is_anonymous (:obj:`bool`, optional): Pass :obj:`True`, if the administrator's presence
in the chat is hidden. in the chat is hidden.
can_manage_chat (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
access the chat event log, chat statistics, message statistics in channels, see
channel members, see anonymous administrators in supergroups and ignore slow mode.
Implied by any other administrator privilege.
.. versionadded:: 13.4
can_manage_voice_chats (:obj:`bool`, optional): Pass :obj:`True`, if the administrator
can manage voice chats, supergroups only.
.. versionadded:: 13.4
can_change_info (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can can_change_info (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
change chat title, photo and other settings. change chat title, photo and other settings.
can_post_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can can_post_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
create channel posts, channels only. create channel posts, channels only.
can_edit_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can can_edit_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
edit messages of other users, channels only. edit messages of other users and can pin messages, channels only.
can_delete_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can can_delete_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
delete messages of other users. delete messages of other users.
can_invite_users (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can can_invite_users (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
@ -3651,6 +3679,10 @@ class Bot(TelegramObject):
data['can_pin_messages'] = can_pin_messages data['can_pin_messages'] = can_pin_messages
if can_promote_members is not None: if can_promote_members is not None:
data['can_promote_members'] = can_promote_members data['can_promote_members'] = can_promote_members
if can_manage_chat is not None:
data['can_manage_chat'] = can_manage_chat
if can_manage_voice_chats is not None:
data['can_manage_voice_chats'] = can_manage_voice_chats
result = self._post('promoteChatMember', data, timeout=timeout, api_kwargs=api_kwargs) result = self._post('promoteChatMember', data, timeout=timeout, api_kwargs=api_kwargs)
@ -3740,9 +3772,9 @@ class Bot(TelegramObject):
api_kwargs: JSONDict = None, api_kwargs: JSONDict = None,
) -> str: ) -> str:
""" """
Use this method to generate a new invite link for a chat; any previously generated link Use this method to generate a new primary invite link for a chat; any previously generated
is revoked. The bot must be an administrator in the chat for this to work and must have link is revoked. The bot must be an administrator in the chat for this to work and must
the appropriate admin rights. have the appropriate admin rights.
Args: Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
@ -3753,6 +3785,13 @@ class Bot(TelegramObject):
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API. Telegram API.
Note:
Each administrator in a chat generates their own invite links. Bots can't use invite
links generated by other administrators. If you want your bot to work with invite
links, it will need to generate its own link using :meth:`export_chat_invite_link` or
by calling the :meth:`get_chat` method. If your bot needs to generate a new primary
invite link replacing its previous one, use :attr:`export_chat_invite_link` again.
Returns: Returns:
:obj:`str`: New invite link on success. :obj:`str`: New invite link on success.
@ -3766,6 +3805,155 @@ class Bot(TelegramObject):
return result # type: ignore[return-value] return result # type: ignore[return-value]
@log
def create_chat_invite_link(
self,
chat_id: Union[str, int],
expire_date: Union[int, datetime] = None,
member_limit: int = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> ChatInviteLink:
"""
Use this method to create an additional invite link for a chat. The bot must be an
administrator in the chat for this to work and must have the appropriate admin rights.
The link can be revoked using the method :meth:`revoke_chat_invite_link`.
.. versionadded:: 13.4
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
expire_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the link will
expire.
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
bot will be used.
member_limit (:obj:`int`, optional): Maximum number of users that can be members of
the chat simultaneously after joining the chat via this invite link; 1-99999.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:class:`telegram.ChatInviteLink`
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
'chat_id': chat_id,
}
if expire_date is not None:
if isinstance(expire_date, datetime):
expire_date = to_timestamp(
expire_date, tzinfo=self.defaults.tzinfo if self.defaults else None
)
data['expire_date'] = expire_date
if member_limit is not None:
data['member_limit'] = member_limit
result = self._post('createChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type]
@log
def edit_chat_invite_link(
self,
chat_id: Union[str, int],
invite_link: str,
expire_date: Union[int, datetime] = None,
member_limit: int = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> ChatInviteLink:
"""
Use this method to edit a non-primary invite link created by the bot. The bot must be an
administrator in the chat for this to work and must have the appropriate admin rights.
.. versionadded:: 13.4
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
invite_link (:obj:`str`): The invite link to edit.
expire_date (:obj:`int` | :obj:`datetime.datetime`, optional): Date when the link will
expire.
For timezone naive :obj:`datetime.datetime` objects, the default timezone of the
bot will be used.
member_limit (:obj:`int`, optional): Maximum number of users that can be members of
the chat simultaneously after joining the chat via this invite link; 1-99999.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:class:`telegram.ChatInviteLink`
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {'chat_id': chat_id, 'invite_link': invite_link}
if expire_date is not None:
if isinstance(expire_date, datetime):
expire_date = to_timestamp(
expire_date, tzinfo=self.defaults.tzinfo if self.defaults else None
)
data['expire_date'] = expire_date
if member_limit is not None:
data['member_limit'] = member_limit
result = self._post('editChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type]
@log
def revoke_chat_invite_link(
self,
chat_id: Union[str, int],
invite_link: str,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> ChatInviteLink:
"""
Use this method to revoke an invite link created by the bot. If the primary link is
revoked, a new link is automatically generated. The bot must be an administrator in the
chat for this to work and must have the appropriate admin rights.
.. versionadded:: 13.4
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
invite_link (:obj:`str`): The invite link to edit.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:class:`telegram.ChatInviteLink`
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {'chat_id': chat_id, 'invite_link': invite_link}
result = self._post('revokeChatInviteLink', data, timeout=timeout, api_kwargs=api_kwargs)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value, arg-type]
@log @log
def set_chat_photo( def set_chat_photo(
self, self,
@ -4061,7 +4249,7 @@ class Bot(TelegramObject):
result = self._post('getStickerSet', data, timeout=timeout, api_kwargs=api_kwargs) result = self._post('getStickerSet', data, timeout=timeout, api_kwargs=api_kwargs)
return StickerSet.de_json(result, self) # type: ignore return StickerSet.de_json(result, self) # type: ignore[return-value, arg-type]
@log @log
def upload_sticker_file( def upload_sticker_file(
@ -4106,7 +4294,7 @@ class Bot(TelegramObject):
result = self._post('uploadStickerFile', data, timeout=timeout, api_kwargs=api_kwargs) result = self._post('uploadStickerFile', data, timeout=timeout, api_kwargs=api_kwargs)
return File.de_json(result, self) # type: ignore return File.de_json(result, self) # type: ignore[return-value, arg-type]
@log @log
def create_new_sticker_set( def create_new_sticker_set(
@ -4599,7 +4787,7 @@ class Bot(TelegramObject):
result = self._post('stopPoll', data, timeout=timeout, api_kwargs=api_kwargs) result = self._post('stopPoll', data, timeout=timeout, api_kwargs=api_kwargs)
return Poll.de_json(result, self) # type: ignore return Poll.de_json(result, self) # type: ignore[return-value, arg-type]
@log @log
def send_dice( def send_dice(
@ -4614,15 +4802,18 @@ class Bot(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
) -> Message: ) -> Message:
""" """
Use this method to send an animated emoji, which will have a random value. On success, the Use this method to send an animated emoji that will display a random value.
sent Message is returned.
Args: Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target private chat. chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
emoji (:obj:`str`, optional): Emoji on which the dice throw animation is based. emoji (:obj:`str`, optional): Emoji on which the dice throw animation is based.
Currently, must be one of 🎲, 🎯, 🏀, , or 🎰. Dice can have values 1-6 Currently, must be one of 🎲, 🎯, 🏀, , "🎳", or 🎰. Dice can have
for 🎲 and 🎯, values 1-5 for 🏀 and , and values 1-64 for 🎰. Defaults values 1-6 for 🎲, 🎯 and "🎳", values 1-5 for 🏀 and , and values 1-64
to 🎲. for 🎰. Defaults to 🎲.
.. versionchanged:: 13.4
Added the "🎳" emoji.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound. receive a notification with no sound.
reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the
@ -4852,7 +5043,7 @@ class Bot(TelegramObject):
data['reply_markup'] = reply_markup data['reply_markup'] = reply_markup
result = self._post('copyMessage', data, timeout=timeout, api_kwargs=api_kwargs) result = self._post('copyMessage', data, timeout=timeout, api_kwargs=api_kwargs)
return MessageId.de_json(result, self) # type: ignore return MessageId.de_json(result, self) # type: ignore[return-value, arg-type]
def to_dict(self) -> JSONDict: def to_dict(self) -> JSONDict:
data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name} data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name}
@ -4971,6 +5162,12 @@ class Bot(TelegramObject):
"""Alias for :attr:`set_chat_administrator_custom_title`""" """Alias for :attr:`set_chat_administrator_custom_title`"""
exportChatInviteLink = export_chat_invite_link exportChatInviteLink = export_chat_invite_link
"""Alias for :attr:`export_chat_invite_link`""" """Alias for :attr:`export_chat_invite_link`"""
createChatInviteLink = create_chat_invite_link
"""Alias for :attr:`create_chat_invite_link`"""
editChatInviteLink = edit_chat_invite_link
"""Alias for :attr:`edit_chat_invite_link`"""
revokeChatInviteLink = revoke_chat_invite_link
"""Alias for :attr:`revoke_chat_invite_link`"""
setChatPhoto = set_chat_photo setChatPhoto = set_chat_photo
"""Alias for :attr:`set_chat_photo`""" """Alias for :attr:`set_chat_photo`"""
deleteChatPhoto = delete_chat_photo deleteChatPhoto = delete_chat_photo

View file

@ -47,7 +47,7 @@ class CallbackQuery(TelegramObject):
considered equal, if their :attr:`id` is equal. considered equal, if their :attr:`id` is equal.
Note: Note:
* In Python `from` is a reserved word, use `from_user` instead. * In Python ``from`` is a reserved word, use ``from_user`` instead.
* Exactly one of the fields :attr:`data` or :attr:`game_short_name` will be present. * Exactly one of the fields :attr:`data` or :attr:`game_short_name` will be present.
* After the user presses an inline button, Telegram clients will display a progress bar * After the user presses an inline button, Telegram clients will display a progress bar
until you call :attr:`answer`. It is, therefore, necessary to react until you call :attr:`answer`. It is, therefore, necessary to react

View file

@ -32,6 +32,7 @@ if TYPE_CHECKING:
from telegram import ( from telegram import (
Bot, Bot,
ChatMember, ChatMember,
ChatInviteLink,
Message, Message,
MessageId, MessageId,
ReplyMarkup, ReplyMarkup,
@ -80,10 +81,8 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`. :meth:`telegram.Bot.get_chat`.
description (:obj:`str`, optional): Description, for groups, supergroups and channel chats. description (:obj:`str`, optional): Description, for groups, supergroups and channel chats.
Returned only in :meth:`telegram.Bot.get_chat`. Returned only in :meth:`telegram.Bot.get_chat`.
invite_link (:obj:`str`, optional): Chat invite link, for groups, supergroups and channel invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and
chats. Each administrator in a chat generates their own invite links, so the bot must channel. Returned only in :meth:`telegram.Bot.get_chat`.
first generate the link using ``export_chat_invite_link()``. Returned only
in :meth:`telegram.Bot.get_chat`.
pinned_message (:class:`telegram.Message`, optional): The most recent pinned message pinned_message (:class:`telegram.Message`, optional): The most recent pinned message
(by sending date). Returned only in :meth:`telegram.Bot.get_chat`. (by sending date). Returned only in :meth:`telegram.Bot.get_chat`.
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions, permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
@ -91,6 +90,11 @@ class Chat(TelegramObject):
slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user. consecutive messages sent by each unprivileged user.
Returned only in :meth:`telegram.Bot.get_chat`. Returned only in :meth:`telegram.Bot.get_chat`.
message_auto_delete_time (:obj:`int`, optional): The time after which all messages sent to
the chat will be automatically deleted; in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set. sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set.
Returned only in :meth:`telegram.Bot.get_chat`. Returned only in :meth:`telegram.Bot.get_chat`.
@ -114,7 +118,8 @@ class Chat(TelegramObject):
bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`. :meth:`telegram.Bot.get_chat`.
description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats. description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats.
invite_link (:obj:`str`): Optional. Chat invite link, for supergroups and channel chats. invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and
channel. Returned only in :meth:`telegram.Bot.get_chat`.
pinned_message (:class:`telegram.Message`): Optional. The most recent pinned message pinned_message (:class:`telegram.Message`): Optional. The most recent pinned message
(by sending date). Returned only in :meth:`telegram.Bot.get_chat`. (by sending date). Returned only in :meth:`telegram.Bot.get_chat`.
permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions, permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions,
@ -122,6 +127,11 @@ class Chat(TelegramObject):
slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between
consecutive messages sent by each unprivileged user. Returned only in consecutive messages sent by each unprivileged user. Returned only in
:meth:`telegram.Bot.get_chat`. :meth:`telegram.Bot.get_chat`.
message_auto_delete_time (:obj:`int`): Optional. The time after which all messages sent to
the chat will be automatically deleted; in seconds. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set. sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the
sticker set. sticker set.
@ -162,6 +172,7 @@ class Chat(TelegramObject):
bio: str = None, bio: str = None,
linked_chat_id: int = None, linked_chat_id: int = None,
location: ChatLocation = None, location: ChatLocation = None,
message_auto_delete_time: int = None,
**_kwargs: Any, **_kwargs: Any,
): ):
# Required # Required
@ -181,6 +192,9 @@ class Chat(TelegramObject):
self.pinned_message = pinned_message self.pinned_message = pinned_message
self.permissions = permissions self.permissions = permissions
self.slow_mode_delay = slow_mode_delay self.slow_mode_delay = slow_mode_delay
self.message_auto_delete_time = (
int(message_auto_delete_time) if message_auto_delete_time is not None else None
)
self.sticker_set_name = sticker_set_name self.sticker_set_name = sticker_set_name
self.can_set_sticker_set = can_set_sticker_set self.can_set_sticker_set = can_set_sticker_set
self.linked_chat_id = linked_chat_id self.linked_chat_id = linked_chat_id
@ -216,7 +230,7 @@ class Chat(TelegramObject):
return None return None
@classmethod @classmethod
def de_json(cls, data: JSONDict, bot: 'Bot') -> Optional['Chat']: def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Chat']:
data = cls.parse_data(data) data = cls.parse_data(data)
if not data: if not data:
@ -320,6 +334,7 @@ class Chat(TelegramObject):
timeout: ODVInput[float] = DEFAULT_NONE, timeout: ODVInput[float] = DEFAULT_NONE,
until_date: Union[int, datetime] = None, until_date: Union[int, datetime] = None,
api_kwargs: JSONDict = None, api_kwargs: JSONDict = None,
revoke_messages: bool = None,
) -> bool: ) -> bool:
"""Shortcut for:: """Shortcut for::
@ -343,6 +358,7 @@ class Chat(TelegramObject):
timeout=timeout, timeout=timeout,
until_date=until_date, until_date=until_date,
api_kwargs=api_kwargs, api_kwargs=api_kwargs,
revoke_messages=revoke_messages,
) )
def unban_member( def unban_member(
@ -384,6 +400,8 @@ class Chat(TelegramObject):
timeout: ODVInput[float] = DEFAULT_NONE, timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None, api_kwargs: JSONDict = None,
is_anonymous: bool = None, is_anonymous: bool = None,
can_manage_chat: bool = None,
can_manage_voice_chats: bool = None,
) -> bool: ) -> bool:
"""Shortcut for:: """Shortcut for::
@ -412,6 +430,8 @@ class Chat(TelegramObject):
timeout=timeout, timeout=timeout,
api_kwargs=api_kwargs, api_kwargs=api_kwargs,
is_anonymous=is_anonymous, is_anonymous=is_anonymous,
can_manage_chat=can_manage_chat,
can_manage_voice_chats=can_manage_voice_chats,
) )
def restrict_member( def restrict_member(
@ -1391,3 +1411,106 @@ class Chat(TelegramObject):
timeout=timeout, timeout=timeout,
api_kwargs=api_kwargs, api_kwargs=api_kwargs,
) )
def export_invite_link(
self,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> str:
"""Shortcut for::
bot.export_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.export_chat_invite_link`.
.. versionadded:: 13.4
Returns:
:obj:`str`: New invite link on success.
"""
return self.bot.export_chat_invite_link(
chat_id=self.id, timeout=timeout, api_kwargs=api_kwargs
)
def create_invite_link(
self,
expire_date: Union[int, datetime] = None,
member_limit: int = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> 'ChatInviteLink':
"""Shortcut for::
bot.create_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.create_chat_invite_link`.
.. versionadded:: 13.4
Returns:
:class:`telegram.ChatInviteLink`
"""
return self.bot.create_chat_invite_link(
chat_id=self.id,
expire_date=expire_date,
member_limit=member_limit,
timeout=timeout,
api_kwargs=api_kwargs,
)
def edit_invite_link(
self,
invite_link: str,
expire_date: Union[int, datetime] = None,
member_limit: int = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> 'ChatInviteLink':
"""Shortcut for::
bot.edit_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.edit_chat_invite_link`.
.. versionadded:: 13.4
Returns:
:class:`telegram.ChatInviteLink`
"""
return self.bot.edit_chat_invite_link(
chat_id=self.id,
invite_link=invite_link,
expire_date=expire_date,
member_limit=member_limit,
timeout=timeout,
api_kwargs=api_kwargs,
)
def revoke_invite_link(
self,
invite_link: str,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> 'ChatInviteLink':
"""Shortcut for::
bot.revoke_chat_invite_link(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.revoke_chat_invite_link`.
.. versionadded:: 13.4
Returns:
:class:`telegram.ChatInviteLink`
"""
return self.bot.revoke_chat_invite_link(
chat_id=self.id, invite_link=invite_link, timeout=timeout, api_kwargs=api_kwargs
)

102
telegram/chatinvitelink.py Normal file
View file

@ -0,0 +1,102 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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 an invite link for a chat."""
import datetime
from typing import TYPE_CHECKING, Any, Optional
from telegram import TelegramObject, User
from telegram.utils.helpers import from_timestamp, to_timestamp
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class ChatInviteLink(TelegramObject):
"""This object represents an invite link for a chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`invite_link`, :attr:`creator`, :attr:`is_primary` and
:attr:`is_revoked` are equal.
.. versionadded:: 13.4
Args:
invite_link (:obj:`str`): The invite link.
creator (:class:`telegram.User`): Creator of the link.
is_primary (:obj:`bool`): :obj:`True`, if the link is primary.
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
expire_date (:class:`datetime.datetime`, optional): Date when the link will expire or
has been expired.
member_limit (:obj:`int`, optional): Maximum number of users that can be members of the
chat simultaneously after joining the chat via this invite link; 1-99999.
Attributes:
invite_link (:obj:`str`): The invite link. If the link was created by another chat
administrator, then the second part of the link will be replaced with ``''``.
creator (:class:`telegram.User`): Creator of the link.
is_primary (:obj:`bool`): :obj:`True`, if the link is primary.
is_revoked (:obj:`bool`): :obj:`True`, if the link is revoked.
expire_date (:class:`datetime.datetime`): Optional. Date when the link will expire or
has been expired.
member_limit (:obj:`int`): Optional. Maximum number of users that can be members
of the chat simultaneously after joining the chat via this invite link; 1-99999.
"""
def __init__(
self,
invite_link: str,
creator: User,
is_primary: bool,
is_revoked: bool,
expire_date: datetime.datetime = None,
member_limit: int = None,
**_kwargs: Any,
):
# Required
self.invite_link = invite_link
self.creator = creator
self.is_primary = is_primary
self.is_revoked = is_revoked
# Optionals
self.expire_date = expire_date
self.member_limit = int(member_limit) if member_limit is not None else None
self._id_attrs = (self.invite_link, self.creator, self.is_primary, self.is_revoked)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatInviteLink']:
data = cls.parse_data(data)
if not data:
return None
data['creator'] = User.de_json(data.get('creator'), bot)
data['expire_date'] = from_timestamp(data.get('expire_date', None))
return cls(**data)
def to_dict(self) -> JSONDict:
data = super().to_dict()
data['expire_date'] = to_timestamp(self.expire_date)
return data

View file

@ -46,6 +46,18 @@ class ChatMember(TelegramObject):
restrictions will be lifted for this user. restrictions will be lifted for this user.
can_be_edited (:obj:`bool`, optional): Administrators only. :obj:`True`, if the bot is can_be_edited (:obj:`bool`, optional): Administrators only. :obj:`True`, if the bot is
allowed to edit administrator privileges of that user. allowed to edit administrator privileges of that user.
can_manage_chat (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
administrator can access the chat event log, chat statistics, message statistics in
channels, see channel members, see anonymous administrators in supergroups and ignore
slow mode. Implied by any other administrator privilege.
.. versionadded:: 13.4
can_manage_voice_chats (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
administrator can manage voice chats.
.. versionadded:: 13.4
can_change_info (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`, can_change_info (:obj:`bool`, optional): Administrators and restricted only. :obj:`True`,
if the user can change the chat title, photo and other settings. if the user can change the chat title, photo and other settings.
can_post_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the can_post_messages (:obj:`bool`, optional): Administrators only. :obj:`True`, if the
@ -87,6 +99,17 @@ class ChatMember(TelegramObject):
for this user. for this user.
can_be_edited (:obj:`bool`): Optional. If the bot is allowed to edit administrator can_be_edited (:obj:`bool`): Optional. If the bot is allowed to edit administrator
privileges of that user. privileges of that user.
can_manage_chat (:obj:`bool`): Optional. If the administrator can access the chat event
log, chat statistics, message statistics in channels, see channel members, see
anonymous administrators in supergroups and ignore slow mode.
.. versionadded:: 13.4
can_manage_voice_chats (:obj:`bool`): Optional. if the administrator can manage
voice chats.
.. versionadded:: 13.4
can_change_info (:obj:`bool`): Optional. If the user can change the chat title, photo and can_change_info (:obj:`bool`): Optional. If the user can change the chat title, photo and
other settings. other settings.
can_post_messages (:obj:`bool`): Optional. If the administrator can post in the channel. can_post_messages (:obj:`bool`): Optional. If the administrator can post in the channel.
@ -150,6 +173,8 @@ class ChatMember(TelegramObject):
is_member: bool = None, is_member: bool = None,
custom_title: str = None, custom_title: str = None,
is_anonymous: bool = None, is_anonymous: bool = None,
can_manage_chat: bool = None,
can_manage_voice_chats: bool = None,
**_kwargs: Any, **_kwargs: Any,
): ):
# Required # Required
@ -175,6 +200,8 @@ class ChatMember(TelegramObject):
self.can_send_other_messages = can_send_other_messages self.can_send_other_messages = can_send_other_messages
self.can_add_web_page_previews = can_add_web_page_previews self.can_add_web_page_previews = can_add_web_page_previews
self.is_member = is_member self.is_member = is_member
self.can_manage_chat = can_manage_chat
self.can_manage_voice_chats = can_manage_voice_chats
self._id_attrs = (self.user, self.status) self._id_attrs = (self.user, self.status)

View file

@ -0,0 +1,115 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2021
# 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 ChatMemberUpdated."""
import datetime
from typing import TYPE_CHECKING, Any, Optional
from telegram import TelegramObject, User, Chat, ChatMember, ChatInviteLink
from telegram.utils.helpers import from_timestamp, to_timestamp
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class ChatMemberUpdated(TelegramObject):
"""This object represents changes in the status of a chat member.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`chat`, :attr:`from_user`, :attr:`date`,
:attr:`old_chat_member` and :attr:`new_chat_member` are equal.
.. versionadded:: 13.4
Note:
In Python ``from`` is a reserved word, use ``from_user`` instead.
Args:
chat (:class:`telegram.Chat`): Chat the user belongs to.
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
:class:`datetime.datetime`.
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link, which was used
by the user to join the chat. For joining by invite link events only.
Attributes:
chat (:class:`telegram.Chat`): Chat the user belongs to.
from_user (:class:`telegram.User`): Performer of the action, which resulted in the change.
date (:class:`datetime.datetime`): Date the change was done in Unix time. Converted to
:class:`datetime.datetime`.
old_chat_member (:class:`telegram.ChatMember`): Previous information about the chat member.
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link, which was used
by the user to join the chat.
"""
def __init__(
self,
chat: Chat,
from_user: User,
date: datetime.datetime,
old_chat_member: ChatMember,
new_chat_member: ChatMember,
invite_link: ChatInviteLink = None,
**_kwargs: Any,
):
# Required
self.chat = chat
self.from_user = from_user
self.date = date
self.old_chat_member = old_chat_member
self.new_chat_member = new_chat_member
# Optionals
self.invite_link = invite_link
self._id_attrs = (
self.chat,
self.from_user,
self.date,
self.old_chat_member,
self.new_chat_member,
)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['ChatMemberUpdated']:
data = cls.parse_data(data)
if not data:
return None
data['chat'] = Chat.de_json(data.get('chat'), bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['date'] = from_timestamp(data.get('date'))
data['old_chat_member'] = ChatMember.de_json(data.get('old_chat_member'), bot)
data['new_chat_member'] = ChatMember.de_json(data.get('new_chat_member'), bot)
data['invite_link'] = ChatInviteLink.de_json(data.get('invite_link'), bot)
return cls(**data)
def to_dict(self) -> JSONDict:
data = super().to_dict()
# Required
data['date'] = to_timestamp(self.date)
return data

View file

@ -37,7 +37,7 @@ class ChosenInlineResult(TelegramObject):
considered equal, if their :attr:`result_id` is equal. considered equal, if their :attr:`result_id` is equal.
Note: Note:
* In Python `from` is a reserved word, use `from_user` instead. * In Python ``from`` is a reserved word, use ``from_user`` instead.
* It is necessary to enable inline feedback via `@Botfather <https://t.me/BotFather>`_ in * It is necessary to enable inline feedback via `@Botfather <https://t.me/BotFather>`_ in
order to receive these objects in updates. order to receive these objects in updates.

View file

@ -21,6 +21,10 @@ The following constants were extracted from the
`Telegram Bots API <https://core.telegram.org/bots/api>`_. `Telegram Bots API <https://core.telegram.org/bots/api>`_.
Attributes: Attributes:
BOT_API_VERSION (:obj:`str`): `5.1`. Telegram Bot API version supported by this
version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``.
.. versionadded:: 13.4
MAX_MESSAGE_LENGTH (:obj:`int`): 4096 MAX_MESSAGE_LENGTH (:obj:`int`): 4096
MAX_CAPTION_LENGTH (:obj:`int`): 1024 MAX_CAPTION_LENGTH (:obj:`int`): 1024
SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443] SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443]
@ -88,8 +92,14 @@ Attributes:
DICE_BASKETBALL (:obj:`str`): '🏀' DICE_BASKETBALL (:obj:`str`): '🏀'
DICE_FOOTBALL (:obj:`str`): '' DICE_FOOTBALL (:obj:`str`): ''
DICE_SLOT_MACHINE (:obj:`str`): '🎰' DICE_SLOT_MACHINE (:obj:`str`): '🎰'
DICE_BOWLING (:obj:`str`): '🎳'
.. versionadded:: 13.4
DICE_ALL_EMOJI (List[:obj:`str`]): List of all supported base emoji. DICE_ALL_EMOJI (List[:obj:`str`]): List of all supported base emoji.
.. versionchanged:: 13.4
Added :attr:`DICE_BOWLING`
:class:`telegram.MessageEntity`: :class:`telegram.MessageEntity`:
Attributes: Attributes:
@ -136,6 +146,7 @@ Attributes:
""" """
from typing import List from typing import List
BOT_API_VERSION: str = '5.1'
MAX_MESSAGE_LENGTH: int = 4096 MAX_MESSAGE_LENGTH: int = 4096
MAX_CAPTION_LENGTH: int = 1024 MAX_CAPTION_LENGTH: int = 1024
ANONYMOUS_ADMIN_ID: int = 1087968824 ANONYMOUS_ADMIN_ID: int = 1087968824
@ -182,12 +193,14 @@ DICE_DARTS: str = '🎯'
DICE_BASKETBALL: str = '🏀' DICE_BASKETBALL: str = '🏀'
DICE_FOOTBALL: str = '' DICE_FOOTBALL: str = ''
DICE_SLOT_MACHINE: str = '🎰' DICE_SLOT_MACHINE: str = '🎰'
DICE_BOWLING: str = '🎳'
DICE_ALL_EMOJI: List[str] = [ DICE_ALL_EMOJI: List[str] = [
DICE_DICE, DICE_DICE,
DICE_DARTS, DICE_DARTS,
DICE_BASKETBALL, DICE_BASKETBALL,
DICE_FOOTBALL, DICE_FOOTBALL,
DICE_SLOT_MACHINE, DICE_SLOT_MACHINE,
DICE_BOWLING,
] ]
MESSAGEENTITY_MENTION: str = 'mention' MESSAGEENTITY_MENTION: str = 'mention'

View file

@ -45,13 +45,17 @@ class Dice(TelegramObject):
3 indicates that the goal was missed. However, this behaviour is undocumented and might 3 indicates that the goal was missed. However, this behaviour is undocumented and might
be changed by Telegram. be changed by Telegram.
If :attr:`emoji` is "🎳", a value of 6 knocks all the pins, while a value of 1 means all
the pins were missed. However, this behaviour is undocumented and might be changed by
Telegram.
If :attr:`emoji` is "🎰", each value corresponds to a unique combination of symbols, which If :attr:`emoji` is "🎰", each value corresponds to a unique combination of symbols, which
can be found at our `wiki <https://git.io/JkeC6>`_. However, this behaviour is undocumented can be found at our `wiki <https://git.io/JkeC6>`_. However, this behaviour is undocumented
and might be changed by Telegram. and might be changed by Telegram.
Args: Args:
value (:obj:`int`): Value of the dice. 1-6 for dice and darts, 1-5 for basketball and value (:obj:`int`): Value of the dice. 1-6 for dice, darts and bowling balls, 1-5 for
football/soccer ball, 1-64 for slot machine. basketball and football/soccer ball, 1-64 for slot machine.
emoji (:obj:`str`): Emoji on which the dice throw animation is based. emoji (:obj:`str`): Emoji on which the dice throw animation is based.
Attributes: Attributes:
@ -76,5 +80,11 @@ class Dice(TelegramObject):
""":const:`telegram.constants.DICE_FOOTBALL`""" """:const:`telegram.constants.DICE_FOOTBALL`"""
SLOT_MACHINE: ClassVar[str] = constants.DICE_SLOT_MACHINE SLOT_MACHINE: ClassVar[str] = constants.DICE_SLOT_MACHINE
""":const:`telegram.constants.DICE_SLOT_MACHINE`""" """:const:`telegram.constants.DICE_SLOT_MACHINE`"""
BOWLING: ClassVar[str] = constants.DICE_BOWLING
"""
:const:`telegram.constants.DICE_BOWLING`
.. versionadded:: 13.4
"""
ALL_EMOJI: ClassVar[List[str]] = constants.DICE_ALL_EMOJI ALL_EMOJI: ClassVar[List[str]] = constants.DICE_ALL_EMOJI
""":const:`telegram.constants.DICE_ALL_EMOJI`""" """:const:`telegram.constants.DICE_ALL_EMOJI`"""

View file

@ -43,6 +43,7 @@ from .messagequeue import MessageQueue
from .messagequeue import DelayQueue from .messagequeue import DelayQueue
from .pollanswerhandler import PollAnswerHandler from .pollanswerhandler import PollAnswerHandler
from .pollhandler import PollHandler from .pollhandler import PollHandler
from .chatmemberhandler import ChatMemberHandler
from .defaults import Defaults from .defaults import Defaults
__all__ = ( __all__ = (
@ -78,5 +79,6 @@ __all__ = (
'PrefixHandler', 'PrefixHandler',
'PollAnswerHandler', 'PollAnswerHandler',
'PollHandler', 'PollHandler',
'ChatMemberHandler',
'Defaults', 'Defaults',
) )

View file

@ -0,0 +1,146 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2019-2021
# 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 ChatMemberHandler classes."""
from typing import ClassVar, TypeVar, Union, Callable, TYPE_CHECKING
from telegram import Update
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
from .handler import Handler
if TYPE_CHECKING:
from telegram.ext import CallbackContext
RT = TypeVar('RT')
class ChatMemberHandler(Handler[Update]):
"""Handler class to handle Telegram updates that contain a chat member update.
.. versionadded:: 13.4
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
Note that this is DEPRECATED, and you should use context based callbacks. See
https://git.io/fxJuV for more info.
Warning:
When setting ``run_async`` to :obj:`True`, you cannot rely on adding custom
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
Args:
callback (:obj:`callable`): The callback function for this handler. Will be called when
:attr:`check_update` has determined that an update should be processed by this handler.
Callback signature for context based API:
``def callback(update: Update, context: CallbackContext)``
The return value of the callback is usually ignored except for the special case of
:class:`telegram.ext.ConversationHandler`.
chat_member_types (:obj:`int`, optional): Pass one of :attr:`MY_CHAT_MEMBER`,
:attr:`CHAT_MEMBER` or :attr:`ANY_CHAT_MEMBER` to specify if this handler should handle
only updates with :attr:`telegram.Update.my_chat_member`,
:attr:`telegram.Update.chat_member` or both. Defaults to :attr:`MY_CHAT_MEMBER`.
pass_update_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
``update_queue`` will be passed to the callback function. It will be the ``Queue``
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
that contains new updates which can be used to insert updates. Default is :obj:`False`.
DEPRECATED: Please switch to context based callbacks.
pass_job_queue (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
``job_queue`` will be passed to the callback function. It will be a
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
which can be used to schedule new jobs. Default is :obj:`False`.
DEPRECATED: Please switch to context based callbacks.
pass_user_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
``user_data`` will be passed to the callback function. Default is :obj:`False`.
DEPRECATED: Please switch to context based callbacks.
pass_chat_data (:obj:`bool`, optional): If set to :obj:`True`, a keyword argument called
``chat_data`` will be passed to the callback function. Default is :obj:`False`.
DEPRECATED: Please switch to context based callbacks.
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
Defaults to :obj:`False`.
Attributes:
callback (:obj:`callable`): The callback function for this handler.
chat_member_types (:obj:`int`, optional): Specifies if this handler should handle
only updates with :attr:`telegram.Update.my_chat_member`,
:attr:`telegram.Update.chat_member` or both.
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
passed to the callback function.
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
the callback function.
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
the callback function.
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
the callback function.
run_async (:obj:`bool`): Determines whether the callback will run asynchronously.
"""
MY_CHAT_MEMBER: ClassVar[int] = -1
""":obj:`int`: Used as a constant to handle only :attr:`telegram.Update.my_chat_member`."""
CHAT_MEMBER: ClassVar[int] = 0
""":obj:`int`: Used as a constant to handle only :attr:`telegram.Update.chat_member`."""
ANY_CHAT_MEMBER: ClassVar[int] = 1
""":obj:`int`: Used as a constant to handle bot :attr:`telegram.Update.my_chat_member`
and :attr:`telegram.Update.chat_member`."""
def __init__(
self,
callback: Callable[[Update, 'CallbackContext'], RT],
chat_member_types: int = MY_CHAT_MEMBER,
pass_update_queue: bool = False,
pass_job_queue: bool = False,
pass_user_data: bool = False,
pass_chat_data: bool = False,
run_async: Union[bool, DefaultValue] = DEFAULT_FALSE,
):
super().__init__(
callback,
pass_update_queue=pass_update_queue,
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data,
run_async=run_async,
)
self.chat_member_types = chat_member_types
def check_update(self, update: object) -> bool:
"""Determines whether an update should be passed to this handlers :attr:`callback`.
Args:
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
Returns:
:obj:`bool`
"""
if isinstance(update, Update):
if not (update.my_chat_member or update.chat_member):
return False
if self.chat_member_types == self.ANY_CHAT_MEMBER:
return True
if self.chat_member_types == self.CHAT_MEMBER:
return bool(update.chat_member)
return bool(update.my_chat_member)
return False

View file

@ -1005,6 +1005,15 @@ officedocument.wordprocessingml.document")``.
:attr: `telegram.Message.supergroup_chat_created` or :attr: `telegram.Message.supergroup_chat_created` or
:attr: `telegram.Message.channel_chat_created`.""" :attr: `telegram.Message.channel_chat_created`."""
class _MessageAutoDeleteTimerChanged(MessageFilter):
name = 'MessageAutoDeleteTimerChanged'
def filter(self, message: Message) -> bool:
return bool(message.message_auto_delete_timer_changed)
message_auto_delete_timer_changed = _MessageAutoDeleteTimerChanged()
"""Messages that contain :attr:`message_auto_delete_timer_changed`"""
class _Migrate(MessageFilter): class _Migrate(MessageFilter):
name = 'Filters.status_update.migrate' name = 'Filters.status_update.migrate'
@ -1042,6 +1051,33 @@ officedocument.wordprocessingml.document")``.
proximity_alert_triggered = _ProximityAlertTriggered() proximity_alert_triggered = _ProximityAlertTriggered()
"""Messages that contain :attr:`telegram.Message.proximity_alert_triggered`.""" """Messages that contain :attr:`telegram.Message.proximity_alert_triggered`."""
class _VoiceChatStarted(MessageFilter):
name = 'Filters.status_update.voice_chat_started'
def filter(self, message: Message) -> bool:
return bool(message.voice_chat_started)
voice_chat_started = _VoiceChatStarted()
"""Messages that contain :attr:`telegram.Message.voice_chat_started`."""
class _VoiceChatEnded(MessageFilter):
name = 'Filters.status_update.voice_chat_ended'
def filter(self, message: Message) -> bool:
return bool(message.voice_chat_ended)
voice_chat_ended = _VoiceChatEnded()
"""Messages that contain :attr:`telegram.Message.voice_chat_ended`."""
class _VoiceChatParticipantsInvited(MessageFilter):
name = 'Filters.status_update.voice_chat_participants_invited'
def filter(self, message: Message) -> bool:
return bool(message.voice_chat_participants_invited)
voice_chat_participants_invited = _VoiceChatParticipantsInvited()
"""Messages that contain :attr:`telegram.Message.voice_chat_participants_invited`."""
name = 'Filters.status_update' name = 'Filters.status_update'
def filter(self, message: Update) -> bool: def filter(self, message: Update) -> bool:
@ -1052,10 +1088,14 @@ officedocument.wordprocessingml.document")``.
or self.new_chat_photo(message) or self.new_chat_photo(message)
or self.delete_chat_photo(message) or self.delete_chat_photo(message)
or self.chat_created(message) or self.chat_created(message)
or self.message_auto_delete_timer_changed(message)
or self.migrate(message) or self.migrate(message)
or self.pinned_message(message) or self.pinned_message(message)
or self.connected_website(message) or self.connected_website(message)
or self.proximity_alert_triggered(message) or self.proximity_alert_triggered(message)
or self.voice_chat_started(message)
or self.voice_chat_ended(message)
or self.voice_chat_participants_invited(message)
) )
status_update = _StatusUpdate() status_update = _StatusUpdate()
@ -1085,10 +1125,27 @@ officedocument.wordprocessingml.document")``.
:attr:`telegram.Message.new_chat_photo`. :attr:`telegram.Message.new_chat_photo`.
new_chat_title: Messages that contain new_chat_title: Messages that contain
:attr:`telegram.Message.new_chat_title`. :attr:`telegram.Message.new_chat_title`.
message_auto_delete_timer_changed: Messages that contain
:attr:`message_auto_delete_timer_changed`.
.. versionadded:: 13.4
pinned_message: Messages that contain pinned_message: Messages that contain
:attr:`telegram.Message.pinned_message`. :attr:`telegram.Message.pinned_message`.
proximity_alert_triggered: Messages that contain proximity_alert_triggered: Messages that contain
:attr:`telegram.Message.proximity_alert_triggered`. :attr:`telegram.Message.proximity_alert_triggered`.
voice_chat_started: Messages that contain
:attr:`telegram.Message.voice_chat_started`.
.. versionadded:: 13.4
voice_chat_ended: Messages that contain
:attr:`telegram.Message.voice_chat_ended`.
.. versionadded:: 13.4
voice_chat_participants_invited: Messages that contain
:attr:`telegram.Message.voice_chat_participants_invited`.
.. versionadded:: 13.4
""" """
class _Forwarded(MessageFilter): class _Forwarded(MessageFilter):
@ -1831,6 +1888,7 @@ officedocument.wordprocessingml.document")``.
basketball = _DiceEmoji('🏀', 'basketball') basketball = _DiceEmoji('🏀', 'basketball')
football = _DiceEmoji('') football = _DiceEmoji('')
slot_machine = _DiceEmoji('🎰') slot_machine = _DiceEmoji('🎰')
bowling = _DiceEmoji('🎳', 'bowling')
dice = _Dice() dice = _Dice()
"""Dice Messages. If an integer or a list of integers is passed, it filters messages to only """Dice Messages. If an integer or a list of integers is passed, it filters messages to only
@ -1863,6 +1921,11 @@ officedocument.wordprocessingml.document")``.
as for :attr:`Filters.dice`. as for :attr:`Filters.dice`.
slot_machine: Dice messages with the emoji 🎰. Passing a list of integers is supported just slot_machine: Dice messages with the emoji 🎰. Passing a list of integers is supported just
as for :attr:`Filters.dice`. as for :attr:`Filters.dice`.
bowling: Dice messages with the emoji 🎳. Passing a list of integers is supported just
as for :attr:`Filters.dice`.
.. versionadded:: 13.4
""" """
class language(MessageFilter): class language(MessageFilter):

View file

@ -38,7 +38,7 @@ class InlineQuery(TelegramObject):
considered equal, if their :attr:`id` is equal. considered equal, if their :attr:`id` is equal.
Note: Note:
* In Python `from` is a reserved word, use `from_user` instead. * In Python ``from`` is a reserved word, use ``from_user`` instead.
Args: Args:
id (:obj:`str`): Unique identifier for this query. id (:obj:`str`): Unique identifier for this query.

View file

@ -31,6 +31,10 @@ class InlineQueryResult(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal. considered equal, if their :attr:`id` is equal.
Note:
All URLs passed in inline query results will be available to end users and therefore must
be assumed to be public.
Args: Args:
type (:obj:`str`): Type of the result. type (:obj:`str`): Type of the result.
id (:obj:`str`): Unique identifier for this result, 1-64 Bytes. id (:obj:`str`): Unique identifier for this result, 1-64 Bytes.

View file

@ -47,8 +47,12 @@ from telegram import (
Video, Video,
VideoNote, VideoNote,
Voice, Voice,
VoiceChatStarted,
VoiceChatEnded,
VoiceChatParticipantsInvited,
ProximityAlertTriggered, ProximityAlertTriggered,
ReplyMarkup, ReplyMarkup,
MessageAutoDeleteTimerChanged,
) )
from telegram.utils.helpers import ( from telegram.utils.helpers import (
escape_markdown, escape_markdown,
@ -83,7 +87,7 @@ class Message(TelegramObject):
considered equal, if their :attr:`message_id` and :attr:`chat` are equal. considered equal, if their :attr:`message_id` and :attr:`chat` are equal.
Note: Note:
In Python `from` is a reserved word, use `from_user` instead. In Python ``from`` is a reserved word, use ``from_user`` instead.
Args: Args:
message_id (:obj:`int`): Unique message identifier inside this chat. message_id (:obj:`int`): Unique message identifier inside this chat.
@ -165,6 +169,10 @@ class Message(TelegramObject):
created. This field can't be received in a message coming through updates, because bot created. This field can't be received in a message coming through updates, because bot
can't be a member of a channel when it is created. It can only be found in can't be a member of a channel when it is created. It can only be found in
:attr:`reply_to_message` if someone replies to a very first message in a channel. :attr:`reply_to_message` if someone replies to a very first message in a channel.
message_auto_delete_timer_changed (:class:`telegram.MessageAutoDeleteTimerChanged`, \
optional): Service message: auto-delete timer settings changed in the chat.
.. versionadded:: 13.4
migrate_to_chat_id (:obj:`int`, optional): The group has been migrated to a supergroup with migrate_to_chat_id (:obj:`int`, optional): The group has been migrated to a supergroup with
the specified identifier. This number may be greater than 32 bits and some programming the specified identifier. This number may be greater than 32 bits and some programming
languages may have difficulty/silent defects in interpreting it. But it is smaller than languages may have difficulty/silent defects in interpreting it. But it is smaller than
@ -196,6 +204,18 @@ class Message(TelegramObject):
proximity_alert_triggered (:class:`telegram.ProximityAlertTriggered`, optional): Service proximity_alert_triggered (:class:`telegram.ProximityAlertTriggered`, optional): Service
message. A user in the chat triggered another user's proximity alert while sharing message. A user in the chat triggered another user's proximity alert while sharing
Live Location. Live Location.
voice_chat_started (:class:`telegram.VoiceChatStarted`, optional): Service message: voice
chat started.
.. versionadded:: 13.4
voice_chat_ended (:class:`telegram.VoiceChatEnded`, optional): Service message: voice chat
ended.
.. versionadded:: 13.4
voice_chat_participants_invited (:class:`telegram.VoiceChatParticipantsInvited` optional):
Service message: new participants invited to a voice chat.
.. versionadded:: 13.4
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. ``login_url`` buttons are represented as ordinary url buttons. to the message. ``login_url`` buttons are represented as ordinary url buttons.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
@ -257,6 +277,10 @@ class Message(TelegramObject):
group_chat_created (:obj:`bool`): Optional. The group has been created. group_chat_created (:obj:`bool`): Optional. The group has been created.
supergroup_chat_created (:obj:`bool`): Optional. The supergroup has been created. supergroup_chat_created (:obj:`bool`): Optional. The supergroup has been created.
channel_chat_created (:obj:`bool`): Optional. The channel has been created. channel_chat_created (:obj:`bool`): Optional. The channel has been created.
message_auto_delete_timer_changed (:class:`telegram.MessageAutoDeleteTimerChanged`):
Optional. Service message: auto-delete timer settings changed in the chat.
.. versionadded:: 13.4
migrate_to_chat_id (:obj:`int`): Optional. The group has been migrated to a supergroup with migrate_to_chat_id (:obj:`int`): Optional. The group has been migrated to a supergroup with
the specified identifier. the specified identifier.
migrate_from_chat_id (:obj:`int`): Optional. The supergroup has been migrated from a group migrate_from_chat_id (:obj:`int`): Optional. The supergroup has been migrated from a group
@ -281,6 +305,18 @@ class Message(TelegramObject):
proximity_alert_triggered (:class:`telegram.ProximityAlertTriggered`): Optional. Service proximity_alert_triggered (:class:`telegram.ProximityAlertTriggered`): Optional. Service
message. A user in the chat triggered another user's proximity alert while sharing message. A user in the chat triggered another user's proximity alert while sharing
Live Location. Live Location.
voice_chat_started (:class:`telegram.VoiceChatStarted`): Optional. Service message: voice
chat started
.. versionadded:: 13.4
voice_chat_ended (:class:`telegram.VoiceChatEnded`): Optional. Service message: voice chat
ended.
.. versionadded:: 13.4
voice_chat_participants_invited (:class:`telegram.VoiceChatParticipantsInvited`): Optional.
Service message: new participants invited to a voice chat.
.. versionadded:: 13.4
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
@ -316,6 +352,7 @@ class Message(TelegramObject):
'group_chat_created', 'group_chat_created',
'supergroup_chat_created', 'supergroup_chat_created',
'channel_chat_created', 'channel_chat_created',
'message_auto_delete_timer_changed',
'migrate_to_chat_id', 'migrate_to_chat_id',
'migrate_from_chat_id', 'migrate_from_chat_id',
'pinned_message', 'pinned_message',
@ -323,6 +360,9 @@ class Message(TelegramObject):
'dice', 'dice',
'passport_data', 'passport_data',
'proximity_alert_triggered', 'proximity_alert_triggered',
'voice_chat_started',
'voice_chat_ended',
'voice_chat_participants_invited',
] + ATTACHMENT_TYPES ] + ATTACHMENT_TYPES
def __init__( def __init__(
@ -379,6 +419,10 @@ class Message(TelegramObject):
via_bot: User = None, via_bot: User = None,
proximity_alert_triggered: ProximityAlertTriggered = None, proximity_alert_triggered: ProximityAlertTriggered = None,
sender_chat: Chat = None, sender_chat: Chat = None,
voice_chat_started: VoiceChatStarted = None,
voice_chat_ended: VoiceChatEnded = None,
voice_chat_participants_invited: VoiceChatParticipantsInvited = None,
message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged = None,
**_kwargs: Any, **_kwargs: Any,
): ):
# Required # Required
@ -418,6 +462,7 @@ class Message(TelegramObject):
self.migrate_to_chat_id = migrate_to_chat_id self.migrate_to_chat_id = migrate_to_chat_id
self.migrate_from_chat_id = migrate_from_chat_id self.migrate_from_chat_id = migrate_from_chat_id
self.channel_chat_created = bool(channel_chat_created) self.channel_chat_created = bool(channel_chat_created)
self.message_auto_delete_timer_changed = message_auto_delete_timer_changed
self.pinned_message = pinned_message self.pinned_message = pinned_message
self.forward_from_message_id = forward_from_message_id self.forward_from_message_id = forward_from_message_id
self.invoice = invoice self.invoice = invoice
@ -433,6 +478,9 @@ class Message(TelegramObject):
self.dice = dice self.dice = dice
self.via_bot = via_bot self.via_bot = via_bot
self.proximity_alert_triggered = proximity_alert_triggered self.proximity_alert_triggered = proximity_alert_triggered
self.voice_chat_started = voice_chat_started
self.voice_chat_ended = voice_chat_ended
self.voice_chat_participants_invited = voice_chat_participants_invited
self.reply_markup = reply_markup self.reply_markup = reply_markup
self.bot = bot self.bot = bot
@ -489,6 +537,9 @@ class Message(TelegramObject):
data['new_chat_members'] = User.de_list(data.get('new_chat_members'), bot) data['new_chat_members'] = User.de_list(data.get('new_chat_members'), bot)
data['left_chat_member'] = User.de_json(data.get('left_chat_member'), bot) data['left_chat_member'] = User.de_json(data.get('left_chat_member'), bot)
data['new_chat_photo'] = PhotoSize.de_list(data.get('new_chat_photo'), bot) data['new_chat_photo'] = PhotoSize.de_list(data.get('new_chat_photo'), bot)
data['message_auto_delete_timer_changed'] = MessageAutoDeleteTimerChanged.de_json(
data.get('message_auto_delete_timer_changed'), bot
)
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot) data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
data['invoice'] = Invoice.de_json(data.get('invoice'), bot) data['invoice'] = Invoice.de_json(data.get('invoice'), bot)
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot) data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot)
@ -500,7 +551,11 @@ class Message(TelegramObject):
data.get('proximity_alert_triggered'), bot data.get('proximity_alert_triggered'), bot
) )
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot) data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
data['voice_chat_started'] = VoiceChatStarted.de_json(data.get('voice_chat_started'), bot)
data['voice_chat_ended'] = VoiceChatEnded.de_json(data.get('voice_chat_ended'), bot)
data['voice_chat_participants_invited'] = VoiceChatParticipantsInvited.de_json(
data.get('voice_chat_participants_invited'), bot
)
return cls(bot=bot, **data) return cls(bot=bot, **data)
@property @property

View file

@ -0,0 +1,53 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2021
# 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 change in the Telegram message auto
deletion."""
from typing import Any
from telegram import TelegramObject
class MessageAutoDeleteTimerChanged(TelegramObject):
"""This object represents a service message about a change in auto-delete timer settings.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`message_auto_delete_time` is equal.
.. versionadded:: 13.4
Args:
message_auto_delete_time (:obj:`int`): New auto-delete time for messages in the
chat.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
message_auto_delete_time (:obj:`int`): New auto-delete time for messages in the
chat.
"""
def __init__(
self,
message_auto_delete_time: int,
**_kwargs: Any,
):
self.message_auto_delete_time = int(message_auto_delete_time)
self._id_attrs = (self.message_auto_delete_time,)

View file

@ -35,7 +35,7 @@ class PreCheckoutQuery(TelegramObject):
considered equal, if their :attr:`id` is equal. considered equal, if their :attr:`id` is equal.
Note: Note:
In Python `from` is a reserved word, use `from_user` instead. In Python ``from`` is a reserved word, use ``from_user`` instead.
Args: Args:
id (:obj:`str`): Unique query identifier. id (:obj:`str`): Unique query identifier.

View file

@ -35,7 +35,7 @@ class ShippingQuery(TelegramObject):
considered equal, if their :attr:`id` is equal. considered equal, if their :attr:`id` is equal.
Note: Note:
In Python `from` is a reserved word, use `from_user` instead. In Python ``from`` is a reserved word, use ``from_user`` instead.
Args: Args:
id (:obj:`str`): Unique query identifier. id (:obj:`str`): Unique query identifier.

View file

@ -29,6 +29,7 @@ from telegram import (
PreCheckoutQuery, PreCheckoutQuery,
ShippingQuery, ShippingQuery,
TelegramObject, TelegramObject,
ChatMemberUpdated,
) )
from telegram.poll import PollAnswer from telegram.poll import PollAnswer
from telegram.utils.types import JSONDict from telegram.utils.types import JSONDict
@ -74,6 +75,19 @@ class Update(TelegramObject):
poll_answer (:class:`telegram.PollAnswer`, optional): A user changed their answer poll_answer (:class:`telegram.PollAnswer`, optional): A user changed their answer
in a non-anonymous poll. Bots receive new votes only in polls that were sent in a non-anonymous poll. Bots receive new votes only in polls that were sent
by the bot itself. by the bot itself.
my_chat_member (:class:`telegram.ChatMemberUpdated`, optional): The bot's chat member
status was updated in a chat. For private chats, this update is received only when the
bot is blocked or unblocked by the user.
.. versionadded:: 13.4
chat_member (:class:`telegram.ChatMemberUpdated`, optional): A chat member's status was
updated in a chat. The bot must be an administrator in the chat and must explicitly
specify ``'chat_member'`` in the list of ``'allowed_updates'`` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Updater.start_polling` and
:meth:`telegram.ext.Updater.start_webhook`).
.. versionadded:: 13.4
**kwargs (:obj:`dict`): Arbitrary keyword arguments. **kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes: Attributes:
@ -94,6 +108,19 @@ class Update(TelegramObject):
poll_answer (:class:`telegram.PollAnswer`): Optional. A user changed their answer poll_answer (:class:`telegram.PollAnswer`): Optional. A user changed their answer
in a non-anonymous poll. Bots receive new votes only in polls that were sent in a non-anonymous poll. Bots receive new votes only in polls that were sent
by the bot itself. by the bot itself.
my_chat_member (:class:`telegram.ChatMemberUpdated`): Optional. The bot's chat member
status was updated in a chat. For private chats, this update is received only when the
bot is blocked or unblocked by the user.
.. versionadded:: 13.4
chat_member (:class:`telegram.ChatMemberUpdated`): Optional. A chat member's status was
updated in a chat. The bot must be an administrator in the chat and must explicitly
specify ``'chat_member'`` in the list of ``'allowed_updates'`` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Updater.start_polling` and
:meth:`telegram.ext.Updater.start_webhook`).
.. versionadded:: 13.4
""" """
@ -111,6 +138,8 @@ class Update(TelegramObject):
pre_checkout_query: PreCheckoutQuery = None, pre_checkout_query: PreCheckoutQuery = None,
poll: Poll = None, poll: Poll = None,
poll_answer: PollAnswer = None, poll_answer: PollAnswer = None,
my_chat_member: ChatMemberUpdated = None,
chat_member: ChatMemberUpdated = None,
**_kwargs: Any, **_kwargs: Any,
): ):
# Required # Required
@ -127,6 +156,8 @@ class Update(TelegramObject):
self.edited_channel_post = edited_channel_post self.edited_channel_post = edited_channel_post
self.poll = poll self.poll = poll
self.poll_answer = poll_answer self.poll_answer = poll_answer
self.my_chat_member = my_chat_member
self.chat_member = chat_member
self._effective_user: Optional['User'] = None self._effective_user: Optional['User'] = None
self._effective_chat: Optional['Chat'] = None self._effective_chat: Optional['Chat'] = None
@ -170,6 +201,12 @@ class Update(TelegramObject):
elif self.poll_answer: elif self.poll_answer:
user = self.poll_answer.user user = self.poll_answer.user
elif self.my_chat_member:
user = self.my_chat_member.from_user
elif self.chat_member:
user = self.chat_member.from_user
self._effective_user = user self._effective_user = user
return user return user
@ -203,6 +240,12 @@ class Update(TelegramObject):
elif self.edited_channel_post: elif self.edited_channel_post:
chat = self.edited_channel_post.chat chat = self.edited_channel_post.chat
elif self.my_chat_member:
chat = self.my_chat_member.chat
elif self.chat_member:
chat = self.chat_member.chat
self._effective_chat = chat self._effective_chat = chat
return chat return chat
@ -259,5 +302,7 @@ class Update(TelegramObject):
data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot) data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot)
data['poll'] = Poll.de_json(data.get('poll'), bot) data['poll'] = Poll.de_json(data.get('poll'), bot)
data['poll_answer'] = PollAnswer.de_json(data.get('poll_answer'), bot) data['poll_answer'] = PollAnswer.de_json(data.get('poll_answer'), bot)
data['my_chat_member'] = ChatMemberUpdated.de_json(data.get('my_chat_member'), bot)
data['chat_member'] = ChatMemberUpdated.de_json(data.get('chat_member'), bot)
return cls(**data) return cls(**data)

View file

@ -18,4 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=C0114 # pylint: disable=C0114
from telegram import constants
__version__ = '13.3' __version__ = '13.3'
bot_api_version = constants.BOT_API_VERSION # pylint: disable=C0103

111
telegram/voicechat.py Normal file
View file

@ -0,0 +1,111 @@
#!/usr/bin/env python
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains objects related to Telegram voice chats."""
from typing import TYPE_CHECKING, Any, Optional, List
from telegram import TelegramObject, User
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class VoiceChatStarted(TelegramObject):
"""
This object represents a service message about a voice
chat started in the chat. Currently holds no information.
.. versionadded:: 13.4
"""
def __init__(self, **_kwargs: Any):
pass
class VoiceChatEnded(TelegramObject):
"""
This object represents a service message about a
voice chat ended in the chat.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`duration` are equal.
.. versionadded:: 13.4
Args:
duration (:obj:`int`): Voice chat duration in seconds.
Attributes:
duration (:obj:`int`): Voice chat duration in seconds.
"""
def __init__(self, duration: int, **_kwargs: Any) -> None:
self.duration = int(duration) if duration is not None else None
self._id_attrs = (self.duration,)
class VoiceChatParticipantsInvited(TelegramObject):
"""
This object represents a service message about
new members invited to a voice chat.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`users` are equal.
.. versionadded:: 13.4
Args:
users (List[:class:`telegram.User`]): New members that
were invited to the voice chat.
Attributes:
users (List[:class:`telegram.User`]): New members that
were invited to the voice chat.
"""
def __init__(self, users: List[User], **_kwargs: Any) -> None:
self.users = users
self._id_attrs = (self.users,)
def __hash__(self) -> int:
return hash(tuple(self.users))
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: 'Bot'
) -> Optional['VoiceChatParticipantsInvited']:
data = cls.parse_data(data)
if not data:
return None
data['users'] = User.de_list(data.get('users', []), bot)
return cls(**data)
def to_dict(self) -> JSONDict:
data = super().to_dict()
data["users"] = [u.to_dict() for u in self.users]
return data

View file

@ -23,6 +23,7 @@ from pathlib import Path
from platform import python_implementation from platform import python_implementation
import pytest import pytest
import pytz
from flaky import flaky from flaky import flaky
from telegram import ( from telegram import (
@ -868,13 +869,14 @@ class TestBot:
assert bot.token not in resulting_path assert bot.token not in resulting_path
assert resulting_path == path assert resulting_path == path
# TODO: Needs improvement. No feasable way to test until bots can add members. # TODO: Needs improvement. No feasible way to test until bots can add members.
def test_kick_chat_member(self, monkeypatch, bot): def test_kick_chat_member(self, monkeypatch, bot):
def test(url, data, *args, **kwargs): def test(url, data, *args, **kwargs):
chat_id = data['chat_id'] == 2 chat_id = data['chat_id'] == 2
user_id = data['user_id'] == 32 user_id = data['user_id'] == 32
until_date = data.get('until_date', 1577887200) == 1577887200 until_date = data.get('until_date', 1577887200) == 1577887200
return chat_id and user_id and until_date revoke_msgs = data.get('revoke_messages', True) is True
return chat_id and user_id and until_date and revoke_msgs
monkeypatch.setattr(bot.request, 'post', test) monkeypatch.setattr(bot.request, 'post', test)
until = from_timestamp(1577887200) until = from_timestamp(1577887200)
@ -882,6 +884,7 @@ class TestBot:
assert bot.kick_chat_member(2, 32) assert bot.kick_chat_member(2, 32)
assert bot.kick_chat_member(2, 32, until_date=until) assert bot.kick_chat_member(2, 32, until_date=until)
assert bot.kick_chat_member(2, 32, until_date=1577887200) assert bot.kick_chat_member(2, 32, until_date=1577887200)
assert bot.kick_chat_member(2, 32, revoke_messages=True)
def test_kick_chat_member_default_tz(self, monkeypatch, tz_bot): def test_kick_chat_member_default_tz(self, monkeypatch, tz_bot):
until = dtm.datetime(2020, 1, 11, 16, 13) until = dtm.datetime(2020, 1, 11, 16, 13)
@ -1497,7 +1500,7 @@ class TestBot:
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
def test_promote_chat_member(self, bot, channel_id): def test_promote_chat_member(self, bot, channel_id, monkeypatch):
# TODO: Add bot to supergroup so this can be tested properly / give bot perms # TODO: Add bot to supergroup so this can be tested properly / give bot perms
with pytest.raises(BadRequest, match='Not enough rights'): with pytest.raises(BadRequest, match='Not enough rights'):
assert bot.promote_chat_member( assert bot.promote_chat_member(
@ -1512,8 +1515,46 @@ class TestBot:
can_restrict_members=True, can_restrict_members=True,
can_pin_messages=True, can_pin_messages=True,
can_promote_members=True, can_promote_members=True,
can_manage_chat=True,
can_manage_voice_chats=True,
) )
# Test that we pass the correct params to TG
def make_assertion(*args, **_):
data = args[1]
return (
data.get('chat_id') == channel_id
and data.get('user_id') == 95205500
and data.get('is_anonymous') == 1
and data.get('can_change_info') == 2
and data.get('can_post_messages') == 3
and data.get('can_edit_messages') == 4
and data.get('can_delete_messages') == 5
and data.get('can_invite_users') == 6
and data.get('can_restrict_members') == 7
and data.get('can_pin_messages') == 8
and data.get('can_promote_members') == 9
and data.get('can_manage_chat') == 10
and data.get('can_manage_voice_chats') == 11
)
monkeypatch.setattr(bot, '_post', make_assertion)
assert bot.promote_chat_member(
channel_id,
95205500,
is_anonymous=1,
can_change_info=2,
can_post_messages=3,
can_edit_messages=4,
can_delete_messages=5,
can_invite_users=6,
can_restrict_members=7,
can_pin_messages=8,
can_promote_members=9,
can_manage_chat=10,
can_manage_voice_chats=11,
)
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
def test_export_chat_invite_link(self, bot, channel_id): def test_export_chat_invite_link(self, bot, channel_id):
@ -1522,6 +1563,72 @@ class TestBot:
assert isinstance(invite_link, str) assert isinstance(invite_link, str)
assert invite_link != '' assert invite_link != ''
@flaky(3, 1)
@pytest.mark.timeout(10)
@pytest.mark.parametrize('datetime', argvalues=[True, False], ids=['datetime', 'integer'])
def test_advanced_chat_invite_links(self, bot, channel_id, datetime):
# we are testing this all in one function in order to save api calls
timestamp = dtm.datetime.utcnow()
add_seconds = dtm.timedelta(0, 70)
time_in_future = timestamp + add_seconds
expire_time = time_in_future if datetime else to_timestamp(time_in_future)
aware_time_in_future = pytz.UTC.localize(time_in_future)
invite_link = bot.create_chat_invite_link(
channel_id, expire_date=expire_time, member_limit=10
)
assert invite_link.invite_link != ''
assert not invite_link.invite_link.endswith('...')
assert pytest.approx(invite_link.expire_date == aware_time_in_future)
assert invite_link.member_limit == 10
add_seconds = dtm.timedelta(0, 80)
time_in_future = timestamp + add_seconds
expire_time = time_in_future if datetime else to_timestamp(time_in_future)
aware_time_in_future = pytz.UTC.localize(time_in_future)
edited_invite_link = bot.edit_chat_invite_link(
channel_id, invite_link.invite_link, expire_date=expire_time, member_limit=20
)
assert edited_invite_link.invite_link == invite_link.invite_link
assert pytest.approx(edited_invite_link.expire_date == aware_time_in_future)
assert edited_invite_link.member_limit == 20
revoked_invite_link = bot.revoke_chat_invite_link(channel_id, invite_link.invite_link)
assert revoked_invite_link.invite_link == invite_link.invite_link
assert revoked_invite_link.is_revoked is True
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_advanced_chat_invite_links_default_tzinfo(self, tz_bot, channel_id):
# we are testing this all in one function in order to save api calls
add_seconds = dtm.timedelta(0, 70)
aware_expire_date = dtm.datetime.now(tz=tz_bot.defaults.tzinfo) + add_seconds
time_in_future = aware_expire_date.replace(tzinfo=None)
invite_link = tz_bot.create_chat_invite_link(
channel_id, expire_date=time_in_future, member_limit=10
)
assert invite_link.invite_link != ''
assert not invite_link.invite_link.endswith('...')
assert pytest.approx(invite_link.expire_date == aware_expire_date)
assert invite_link.member_limit == 10
add_seconds = dtm.timedelta(0, 80)
aware_expire_date += add_seconds
time_in_future = aware_expire_date.replace(tzinfo=None)
edited_invite_link = tz_bot.edit_chat_invite_link(
channel_id, invite_link.invite_link, expire_date=time_in_future, member_limit=20
)
assert edited_invite_link.invite_link == invite_link.invite_link
assert pytest.approx(edited_invite_link.expire_date == aware_expire_date)
assert edited_invite_link.member_limit == 20
revoked_invite_link = tz_bot.revoke_chat_invite_link(channel_id, invite_link.invite_link)
assert revoked_invite_link.invite_link == invite_link.invite_link
assert revoked_invite_link.is_revoked is True
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
def test_set_chat_photo(self, bot, channel_id): def test_set_chat_photo(self, bot, channel_id):

View file

@ -37,6 +37,7 @@ def chat(bot):
can_set_sticker_set=TestChat.can_set_sticker_set, can_set_sticker_set=TestChat.can_set_sticker_set,
permissions=TestChat.permissions, permissions=TestChat.permissions,
slow_mode_delay=TestChat.slow_mode_delay, slow_mode_delay=TestChat.slow_mode_delay,
message_auto_delete_time=TestChat.message_auto_delete_time,
bio=TestChat.bio, bio=TestChat.bio,
linked_chat_id=TestChat.linked_chat_id, linked_chat_id=TestChat.linked_chat_id,
location=TestChat.location, location=TestChat.location,
@ -57,6 +58,7 @@ class TestChat:
can_invite_users=True, can_invite_users=True,
) )
slow_mode_delay = 30 slow_mode_delay = 30
message_auto_delete_time = 42
bio = "I'm a Barbie Girl in a Barbie World" bio = "I'm a Barbie Girl in a Barbie World"
linked_chat_id = 11880 linked_chat_id = 11880
location = ChatLocation(Location(123, 456), 'Barbie World') location = ChatLocation(Location(123, 456), 'Barbie World')
@ -72,6 +74,7 @@ class TestChat:
'can_set_sticker_set': self.can_set_sticker_set, 'can_set_sticker_set': self.can_set_sticker_set,
'permissions': self.permissions.to_dict(), 'permissions': self.permissions.to_dict(),
'slow_mode_delay': self.slow_mode_delay, 'slow_mode_delay': self.slow_mode_delay,
'message_auto_delete_time': self.message_auto_delete_time,
'bio': self.bio, 'bio': self.bio,
'linked_chat_id': self.linked_chat_id, 'linked_chat_id': self.linked_chat_id,
'location': self.location.to_dict(), 'location': self.location.to_dict(),
@ -87,6 +90,7 @@ class TestChat:
assert chat.can_set_sticker_set == self.can_set_sticker_set assert chat.can_set_sticker_set == self.can_set_sticker_set
assert chat.permissions == self.permissions assert chat.permissions == self.permissions
assert chat.slow_mode_delay == self.slow_mode_delay assert chat.slow_mode_delay == self.slow_mode_delay
assert chat.message_auto_delete_time == self.message_auto_delete_time
assert chat.bio == self.bio assert chat.bio == self.bio
assert chat.linked_chat_id == self.linked_chat_id assert chat.linked_chat_id == self.linked_chat_id
assert chat.location.location == self.location.location assert chat.location.location == self.location.location
@ -103,6 +107,7 @@ class TestChat:
assert chat_dict['all_members_are_administrators'] == chat.all_members_are_administrators assert chat_dict['all_members_are_administrators'] == chat.all_members_are_administrators
assert chat_dict['permissions'] == chat.permissions.to_dict() assert chat_dict['permissions'] == chat.permissions.to_dict()
assert chat_dict['slow_mode_delay'] == chat.slow_mode_delay assert chat_dict['slow_mode_delay'] == chat.slow_mode_delay
assert chat_dict['message_auto_delete_time'] == chat.message_auto_delete_time
assert chat_dict['bio'] == chat.bio assert chat_dict['bio'] == chat.bio
assert chat_dict['linked_chat_id'] == chat.linked_chat_id assert chat_dict['linked_chat_id'] == chat.linked_chat_id
assert chat_dict['location'] == chat.location.to_dict() assert chat_dict['location'] == chat.location.to_dict()
@ -556,6 +561,62 @@ class TestChat:
monkeypatch.setattr(chat.bot, 'copy_message', make_assertion) monkeypatch.setattr(chat.bot, 'copy_message', make_assertion)
assert chat.copy_message(chat_id='test_copy', message_id=42) assert chat.copy_message(chat_id='test_copy', message_id=42)
def test_export_invite_link(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id
assert check_shortcut_signature(
Chat.export_invite_link, Bot.export_chat_invite_link, ['chat_id'], []
)
assert check_shortcut_call(chat.export_invite_link, chat.bot, 'export_chat_invite_link')
assert check_defaults_handling(chat.export_invite_link, chat.bot)
monkeypatch.setattr(chat.bot, 'export_chat_invite_link', make_assertion)
assert chat.export_invite_link()
def test_create_invite_link(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id
assert check_shortcut_signature(
Chat.create_invite_link, Bot.create_chat_invite_link, ['chat_id'], []
)
assert check_shortcut_call(chat.create_invite_link, chat.bot, 'create_chat_invite_link')
assert check_defaults_handling(chat.create_invite_link, chat.bot)
monkeypatch.setattr(chat.bot, 'create_chat_invite_link', make_assertion)
assert chat.create_invite_link()
def test_edit_invite_link(self, monkeypatch, chat):
link = "ThisIsALink"
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id and kwargs['invite_link'] == link
assert check_shortcut_signature(
Chat.edit_invite_link, Bot.edit_chat_invite_link, ['chat_id'], []
)
assert check_shortcut_call(chat.edit_invite_link, chat.bot, 'edit_chat_invite_link')
assert check_defaults_handling(chat.edit_invite_link, chat.bot)
monkeypatch.setattr(chat.bot, 'edit_chat_invite_link', make_assertion)
assert chat.edit_invite_link(invite_link=link)
def test_revoke_invite_link(self, monkeypatch, chat):
link = "ThisIsALink"
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id and kwargs['invite_link'] == link
assert check_shortcut_signature(
Chat.revoke_invite_link, Bot.revoke_chat_invite_link, ['chat_id'], []
)
assert check_shortcut_call(chat.revoke_invite_link, chat.bot, 'revoke_chat_invite_link')
assert check_defaults_handling(chat.revoke_invite_link, chat.bot)
monkeypatch.setattr(chat.bot, 'revoke_chat_invite_link', make_assertion)
assert chat.revoke_invite_link(invite_link=link)
def test_equality(self): def test_equality(self):
a = Chat(self.id_, self.title, self.type_) a = Chat(self.id_, self.title, self.type_)
b = Chat(self.id_, self.title, self.type_) b = Chat(self.id_, self.title, self.type_)

View file

@ -0,0 +1,119 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime
import pytest
from telegram import User, ChatInviteLink
from telegram.utils.helpers import to_timestamp
@pytest.fixture(scope='class')
def creator():
return User(1, 'First name', False)
@pytest.fixture(scope='class')
def invite_link(creator):
return ChatInviteLink(
TestChatInviteLink.link,
creator,
TestChatInviteLink.primary,
TestChatInviteLink.revoked,
expire_date=TestChatInviteLink.expire_date,
member_limit=TestChatInviteLink.member_limit,
)
class TestChatInviteLink:
link = "thisialink"
primary = True
revoked = False
expire_date = datetime.datetime.utcnow()
member_limit = 42
def test_de_json_required_args(self, bot, creator):
json_dict = {
'invite_link': self.link,
'creator': creator.to_dict(),
'is_primary': self.primary,
'is_revoked': self.revoked,
}
invite_link = ChatInviteLink.de_json(json_dict, bot)
assert invite_link.invite_link == self.link
assert invite_link.creator == creator
assert invite_link.is_primary == self.primary
assert invite_link.is_revoked == self.revoked
def test_de_json_all_args(self, bot, creator):
json_dict = {
'invite_link': self.link,
'creator': creator.to_dict(),
'is_primary': self.primary,
'is_revoked': self.revoked,
'expire_date': to_timestamp(self.expire_date),
'member_limit': self.member_limit,
}
invite_link = ChatInviteLink.de_json(json_dict, bot)
assert invite_link.invite_link == self.link
assert invite_link.creator == creator
assert invite_link.is_primary == self.primary
assert invite_link.is_revoked == self.revoked
assert pytest.approx(invite_link.expire_date == self.expire_date)
assert to_timestamp(invite_link.expire_date) == to_timestamp(self.expire_date)
assert invite_link.member_limit == self.member_limit
def test_to_dict(self, invite_link):
invite_link_dict = invite_link.to_dict()
assert isinstance(invite_link_dict, dict)
assert invite_link_dict['creator'] == invite_link.creator.to_dict()
assert invite_link_dict['invite_link'] == invite_link.invite_link
assert invite_link_dict['is_primary'] == self.primary
assert invite_link_dict['is_revoked'] == self.revoked
assert invite_link_dict['expire_date'] == to_timestamp(self.expire_date)
assert invite_link_dict['member_limit'] == self.member_limit
def test_equality(self):
a = ChatInviteLink("link", User(1, '', False), True, True)
b = ChatInviteLink("link", User(1, '', False), True, True)
d = ChatInviteLink("link", User(2, '', False), False, True)
d2 = ChatInviteLink("notalink", User(1, '', False), False, True)
d3 = ChatInviteLink("notalink", User(1, '', False), True, True)
e = User(1, '', False)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != d
assert hash(a) != hash(d)
assert a != d2
assert hash(a) != hash(d2)
assert d2 != d3
assert hash(d2) != hash(d3)
assert a != e
assert hash(a) != hash(e)

View file

@ -69,6 +69,8 @@ class TestChatMember:
'can_send_polls': False, 'can_send_polls': False,
'can_send_other_messages': True, 'can_send_other_messages': True,
'can_add_web_page_previews': False, 'can_add_web_page_previews': False,
'can_manage_chat': True,
'can_manage_voice_chats': True,
} }
chat_member = ChatMember.de_json(json_dict, bot) chat_member = ChatMember.de_json(json_dict, bot)
@ -91,6 +93,8 @@ class TestChatMember:
assert chat_member.can_send_polls is False assert chat_member.can_send_polls is False
assert chat_member.can_send_other_messages is True assert chat_member.can_send_other_messages is True
assert chat_member.can_add_web_page_previews is False assert chat_member.can_add_web_page_previews is False
assert chat_member.can_manage_chat is True
assert chat_member.can_manage_voice_chats is True
def test_to_dict(self, chat_member): def test_to_dict(self, chat_member):
chat_member_dict = chat_member.to_dict() chat_member_dict = chat_member.to_dict()

View file

@ -0,0 +1,221 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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 time
from queue import Queue
import pytest
from telegram import (
Update,
Bot,
Message,
User,
Chat,
CallbackQuery,
ChosenInlineResult,
ShippingQuery,
PreCheckoutQuery,
ChatMemberUpdated,
ChatMember,
)
from telegram.ext import CallbackContext, JobQueue, ChatMemberHandler
from telegram.utils.helpers import from_timestamp
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 chat_member_updated():
return ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
from_timestamp(int(time.time())),
ChatMember(User(1, '', False), ChatMember.CREATOR),
ChatMember(User(1, '', False), ChatMember.CREATOR),
)
@pytest.fixture(scope='function')
def chat_member(bot, chat_member_updated):
return Update(0, my_chat_member=chat_member_updated)
class TestChatMemberHandler:
test_flag = False
@pytest.fixture(autouse=True)
def reset(self):
self.test_flag = False
def callback_basic(self, bot, update):
test_bot = isinstance(bot, Bot)
test_update = isinstance(update, Update)
self.test_flag = test_bot and test_update
def callback_data_1(self, bot, update, user_data=None, chat_data=None):
self.test_flag = (user_data is not None) or (chat_data is not None)
def callback_data_2(self, bot, update, user_data=None, chat_data=None):
self.test_flag = (user_data is not None) and (chat_data is not None)
def callback_queue_1(self, bot, update, job_queue=None, update_queue=None):
self.test_flag = (job_queue is not None) or (update_queue is not None)
def callback_queue_2(self, bot, update, job_queue=None, update_queue=None):
self.test_flag = (job_queue is not None) and (update_queue is not None)
def callback_context(self, update, context):
self.test_flag = (
isinstance(context, CallbackContext)
and isinstance(context.bot, Bot)
and isinstance(update, Update)
and isinstance(context.update_queue, Queue)
and isinstance(context.job_queue, JobQueue)
and isinstance(context.user_data, dict)
and isinstance(context.chat_data, dict)
and isinstance(context.bot_data, dict)
and isinstance(update.chat_member or update.my_chat_member, ChatMemberUpdated)
)
def test_basic(self, dp, chat_member):
handler = ChatMemberHandler(self.callback_basic)
dp.add_handler(handler)
assert handler.check_update(chat_member)
dp.process_update(chat_member)
assert self.test_flag
@pytest.mark.parametrize(
argnames=['allowed_types', 'expected'],
argvalues=[
(ChatMemberHandler.MY_CHAT_MEMBER, (True, False)),
(ChatMemberHandler.CHAT_MEMBER, (False, True)),
(ChatMemberHandler.ANY_CHAT_MEMBER, (True, True)),
],
ids=['MY_CHAT_MEMBER', 'CHAT_MEMBER', 'ANY_CHAT_MEMBER'],
)
def test_chat_member_types(
self, dp, chat_member_updated, chat_member, expected, allowed_types
):
result_1, result_2 = expected
handler = ChatMemberHandler(self.callback_basic, chat_member_types=allowed_types)
dp.add_handler(handler)
assert handler.check_update(chat_member) == result_1
dp.process_update(chat_member)
assert self.test_flag == result_1
self.test_flag = False
chat_member.my_chat_member = None
chat_member.chat_member = chat_member_updated
assert handler.check_update(chat_member) == result_2
dp.process_update(chat_member)
assert self.test_flag == result_2
def test_pass_user_or_chat_data(self, dp, chat_member):
handler = ChatMemberHandler(self.callback_data_1, pass_user_data=True)
dp.add_handler(handler)
dp.process_update(chat_member)
assert self.test_flag
dp.remove_handler(handler)
handler = ChatMemberHandler(self.callback_data_1, pass_chat_data=True)
dp.add_handler(handler)
self.test_flag = False
dp.process_update(chat_member)
assert self.test_flag
dp.remove_handler(handler)
handler = ChatMemberHandler(self.callback_data_2, pass_chat_data=True, pass_user_data=True)
dp.add_handler(handler)
self.test_flag = False
dp.process_update(chat_member)
assert self.test_flag
def test_pass_job_or_update_queue(self, dp, chat_member):
handler = ChatMemberHandler(self.callback_queue_1, pass_job_queue=True)
dp.add_handler(handler)
dp.process_update(chat_member)
assert self.test_flag
dp.remove_handler(handler)
handler = ChatMemberHandler(self.callback_queue_1, pass_update_queue=True)
dp.add_handler(handler)
self.test_flag = False
dp.process_update(chat_member)
assert self.test_flag
dp.remove_handler(handler)
handler = ChatMemberHandler(
self.callback_queue_2, pass_job_queue=True, pass_update_queue=True
)
dp.add_handler(handler)
self.test_flag = False
dp.process_update(chat_member)
assert self.test_flag
def test_other_update_types(self, false_update):
handler = ChatMemberHandler(self.callback_basic)
assert not handler.check_update(false_update)
assert not handler.check_update(True)
def test_context(self, cdp, chat_member):
handler = ChatMemberHandler(self.callback_context)
cdp.add_handler(handler)
cdp.process_update(chat_member)
assert self.test_flag

View file

@ -0,0 +1,177 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime
import pytest
import pytz
from telegram import User, ChatMember, Chat, ChatMemberUpdated, ChatInviteLink
from telegram.utils.helpers import to_timestamp
@pytest.fixture(scope='class')
def user():
return User(1, 'First name', False)
@pytest.fixture(scope='class')
def chat():
return Chat(1, Chat.SUPERGROUP, 'Chat')
@pytest.fixture(scope='class')
def old_chat_member(user):
return ChatMember(user, TestChatMemberUpdated.old_status)
@pytest.fixture(scope='class')
def new_chat_member(user):
return ChatMember(user, TestChatMemberUpdated.new_status)
@pytest.fixture(scope='class')
def time():
return datetime.datetime.now(tz=pytz.utc)
@pytest.fixture(scope='class')
def invite_link(user):
return ChatInviteLink('link', user, True, True)
@pytest.fixture(scope='class')
def chat_member_updated(user, chat, old_chat_member, new_chat_member, invite_link, time):
return ChatMemberUpdated(chat, user, time, old_chat_member, new_chat_member, invite_link)
class TestChatMemberUpdated:
old_status = ChatMember.MEMBER
new_status = ChatMember.ADMINISTRATOR
def test_de_json_required_args(self, bot, user, chat, old_chat_member, new_chat_member, time):
json_dict = {
'chat': chat.to_dict(),
'from': user.to_dict(),
'date': to_timestamp(time),
'old_chat_member': old_chat_member.to_dict(),
'new_chat_member': new_chat_member.to_dict(),
}
chat_member_updated = ChatMemberUpdated.de_json(json_dict, bot)
assert chat_member_updated.chat == chat
assert chat_member_updated.from_user == user
assert pytest.approx(chat_member_updated.date == time)
assert to_timestamp(chat_member_updated.date) == to_timestamp(time)
assert chat_member_updated.old_chat_member == old_chat_member
assert chat_member_updated.new_chat_member == new_chat_member
assert chat_member_updated.invite_link is None
def test_de_json_all_args(
self, bot, user, time, invite_link, chat, old_chat_member, new_chat_member
):
json_dict = {
'chat': chat.to_dict(),
'from': user.to_dict(),
'date': to_timestamp(time),
'old_chat_member': old_chat_member.to_dict(),
'new_chat_member': new_chat_member.to_dict(),
'invite_link': invite_link.to_dict(),
}
chat_member_updated = ChatMemberUpdated.de_json(json_dict, bot)
assert chat_member_updated.chat == chat
assert chat_member_updated.from_user == user
assert pytest.approx(chat_member_updated.date == time)
assert to_timestamp(chat_member_updated.date) == to_timestamp(time)
assert chat_member_updated.old_chat_member == old_chat_member
assert chat_member_updated.new_chat_member == new_chat_member
assert chat_member_updated.invite_link == invite_link
def test_to_dict(self, chat_member_updated):
chat_member_updated_dict = chat_member_updated.to_dict()
assert isinstance(chat_member_updated_dict, dict)
assert chat_member_updated_dict['chat'] == chat_member_updated.chat.to_dict()
assert chat_member_updated_dict['from'] == chat_member_updated.from_user.to_dict()
assert chat_member_updated_dict['date'] == to_timestamp(chat_member_updated.date)
assert (
chat_member_updated_dict['old_chat_member']
== chat_member_updated.old_chat_member.to_dict()
)
assert (
chat_member_updated_dict['new_chat_member']
== chat_member_updated.new_chat_member.to_dict()
)
assert chat_member_updated_dict['invite_link'] == chat_member_updated.invite_link.to_dict()
def test_equality(self, time, old_chat_member, new_chat_member, invite_link):
a = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
time,
old_chat_member,
new_chat_member,
invite_link,
)
b = ChatMemberUpdated(
Chat(1, 'chat'), User(1, '', False), time, old_chat_member, new_chat_member
)
# wrong date
c = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
time + datetime.timedelta(hours=1),
old_chat_member,
new_chat_member,
)
# wrong chat & form_user
d = ChatMemberUpdated(
Chat(42, 'wrong_chat'),
User(42, 'wrong_user', False),
time,
old_chat_member,
new_chat_member,
)
# wrong old_chat_member
e = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
time,
ChatMember(User(1, '', False), ChatMember.CREATOR),
new_chat_member,
)
# wrong new_chat_member
f = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
time,
old_chat_member,
ChatMember(User(1, '', False), ChatMember.CREATOR),
)
# wrong type
g = ChatMember(User(1, '', False), ChatMember.CREATOR)
assert a == b
assert hash(a) == hash(b)
assert a is not b
for other in [c, d, e, f, g]:
assert a != other
assert hash(a) != hash(other)

View file

@ -44,7 +44,7 @@ def update():
@pytest.fixture(scope='function', params=MessageEntity.ALL_TYPES) @pytest.fixture(scope='function', params=MessageEntity.ALL_TYPES)
def message_entity(request): def message_entity(request):
return MessageEntity(request.param, 0, 0, url='', user='') return MessageEntity(request.param, 0, 0, url='', user=User(1, 'first_name', False))
@pytest.fixture( @pytest.fixture(
@ -828,6 +828,11 @@ class TestFilters:
assert Filters.status_update.chat_created(update) assert Filters.status_update.chat_created(update)
update.message.channel_chat_created = False update.message.channel_chat_created = False
update.message.message_auto_delete_timer_changed = True
assert Filters.status_update(update)
assert Filters.status_update.message_auto_delete_timer_changed(update)
update.message.message_auto_delete_timer_changed = False
update.message.migrate_to_chat_id = 100 update.message.migrate_to_chat_id = 100
assert Filters.status_update(update) assert Filters.status_update(update)
assert Filters.status_update.migrate(update) assert Filters.status_update.migrate(update)
@ -853,6 +858,21 @@ class TestFilters:
assert Filters.status_update.proximity_alert_triggered(update) assert Filters.status_update.proximity_alert_triggered(update)
update.message.proximity_alert_triggered = None update.message.proximity_alert_triggered = None
update.message.voice_chat_started = 'hello'
assert Filters.status_update(update)
assert Filters.status_update.voice_chat_started(update)
update.message.voice_chat_started = None
update.message.voice_chat_ended = 'bye'
assert Filters.status_update(update)
assert Filters.status_update.voice_chat_ended(update)
update.message.voice_chat_ended = None
update.message.voice_chat_participants_invited = 'invited'
assert Filters.status_update(update)
assert Filters.status_update.voice_chat_participants_invited(update)
update.message.voice_chat_participants_invited = None
def test_filters_forwarded(self, update): def test_filters_forwarded(self, update):
assert not Filters.forwarded(update) assert not Filters.forwarded(update)
update.message.forward_date = datetime.datetime.utcnow() update.message.forward_date = datetime.datetime.utcnow()
@ -1453,6 +1473,13 @@ class TestFilters:
assert not Filters.dice.darts(update) assert not Filters.dice.darts(update)
assert not Filters.dice.slot_machine([4])(update) assert not Filters.dice.slot_machine([4])(update)
update.message.dice = Dice(5, '🎳')
assert Filters.dice.bowling(update)
assert Filters.dice.bowling([4, 5])(update)
assert not Filters.dice.dice(update)
assert not Filters.dice.darts(update)
assert not Filters.dice.bowling([4])(update)
def test_language_filter_single(self, update): def test_language_filter_single(self, update):
update.message.from_user.language_code = 'en_US' update.message.from_user.language_code = 'en_US'
assert (Filters.language('en_US'))(update) assert (Filters.language('en_US'))(update)

View file

@ -48,6 +48,10 @@ from telegram import (
Dice, Dice,
Bot, Bot,
ChatAction, ChatAction,
VoiceChatStarted,
VoiceChatEnded,
VoiceChatParticipantsInvited,
MessageAutoDeleteTimerChanged,
) )
from telegram.ext import Defaults from telegram.ext import Defaults
from tests.conftest import check_shortcut_signature, check_shortcut_call, check_defaults_handling from tests.conftest import check_shortcut_signature, check_shortcut_call, check_defaults_handling
@ -115,6 +119,7 @@ def message(bot):
{'group_chat_created': True}, {'group_chat_created': True},
{'supergroup_chat_created': True}, {'supergroup_chat_created': True},
{'channel_chat_created': True}, {'channel_chat_created': True},
{'message_auto_delete_timer_changed': MessageAutoDeleteTimerChanged(42)},
{'migrate_to_chat_id': -12345}, {'migrate_to_chat_id': -12345},
{'migrate_from_chat_id': -54321}, {'migrate_from_chat_id': -54321},
{'pinned_message': Message(7, None, None, None)}, {'pinned_message': Message(7, None, None, None)},
@ -166,6 +171,13 @@ def message(bot):
User(1, 'John', False), User(2, 'Doe', False), 42 User(1, 'John', False), User(2, 'Doe', False), 42
) )
}, },
{'voice_chat_started': VoiceChatStarted()},
{'voice_chat_ended': VoiceChatEnded(100)},
{
'voice_chat_participants_invited': VoiceChatParticipantsInvited(
[User(1, 'Rem', False), User(2, 'Emilia', False)]
)
},
{'sender_chat': Chat(-123, 'discussion_channel')}, {'sender_chat': Chat(-123, 'discussion_channel')},
], ],
ids=[ ids=[
@ -195,6 +207,7 @@ def message(bot):
'group_created', 'group_created',
'supergroup_created', 'supergroup_created',
'channel_created', 'channel_created',
'message_auto_delete_timer_changed',
'migrated_to', 'migrated_to',
'migrated_from', 'migrated_from',
'pinned', 'pinned',
@ -211,6 +224,9 @@ def message(bot):
'dice', 'dice',
'via_bot', 'via_bot',
'proximity_alert_triggered', 'proximity_alert_triggered',
'voice_chat_started',
'voice_chat_ended',
'voice_chat_participants_invited',
'sender_chat', 'sender_chat',
], ],
) )

View file

@ -0,0 +1,51 @@
#!/usr/bin/env python
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# 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 telegram import MessageAutoDeleteTimerChanged, VoiceChatEnded
class TestMessageAutoDeleteTimerChanged:
message_auto_delete_time = 100
def test_de_json(self):
json_dict = {'message_auto_delete_time': self.message_auto_delete_time}
madtc = MessageAutoDeleteTimerChanged.de_json(json_dict, None)
assert madtc.message_auto_delete_time == self.message_auto_delete_time
def test_to_dict(self):
madtc = MessageAutoDeleteTimerChanged(self.message_auto_delete_time)
madtc_dict = madtc.to_dict()
assert isinstance(madtc_dict, dict)
assert madtc_dict["message_auto_delete_time"] == self.message_auto_delete_time
def test_equality(self):
a = MessageAutoDeleteTimerChanged(100)
b = MessageAutoDeleteTimerChanged(100)
c = MessageAutoDeleteTimerChanged(50)
d = VoiceChatEnded(25)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -16,6 +16,7 @@
# #
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
import time
import pytest import pytest
@ -31,10 +32,20 @@ from telegram import (
PreCheckoutQuery, PreCheckoutQuery,
Poll, Poll,
PollOption, PollOption,
ChatMemberUpdated,
ChatMember,
) )
from telegram.poll import PollAnswer from telegram.poll import PollAnswer
from telegram.utils.helpers import from_timestamp
message = Message(1, None, Chat(1, ''), from_user=User(1, '', False), text='Text') message = Message(1, None, Chat(1, ''), from_user=User(1, '', False), text='Text')
chat_member_updated = ChatMemberUpdated(
Chat(1, 'chat'),
User(1, '', False),
from_timestamp(int(time.time())),
ChatMember(User(1, '', False), ChatMember.CREATOR),
ChatMember(User(1, '', False), ChatMember.CREATOR),
)
params = [ params = [
{'message': message}, {'message': message},
@ -49,6 +60,8 @@ params = [
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')}, {'callback_query': CallbackQuery(1, User(1, '', False), 'chat')},
{'poll': Poll('id', '?', [PollOption('.', 1)], False, False, False, Poll.REGULAR, True)}, {'poll': Poll('id', '?', [PollOption('.', 1)], False, False, False, Poll.REGULAR, True)},
{'poll_answer': PollAnswer("id", User(1, '', False), [1])}, {'poll_answer': PollAnswer("id", User(1, '', False), [1])},
{'my_chat_member': chat_member_updated},
{'chat_member': chat_member_updated},
] ]
all_types = ( all_types = (
@ -63,6 +76,8 @@ all_types = (
'pre_checkout_query', 'pre_checkout_query',
'poll', 'poll',
'poll_answer', 'poll_answer',
'my_chat_member',
'chat_member',
) )
ids = all_types + ('callback_query_without_message',) ids = all_types + ('callback_query_without_message',)
@ -146,6 +161,8 @@ class TestUpdate:
or update.pre_checkout_query is not None or update.pre_checkout_query is not None
or update.poll is not None or update.poll is not None
or update.poll_answer is not None or update.poll_answer is not None
or update.my_chat_member is not None
or update.chat_member is not None
): ):
assert eff_message.message_id == message.message_id assert eff_message.message_id == message.message_id
else: else:

114
tests/test_voicechat.py Normal file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env python
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2021
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import VoiceChatStarted, VoiceChatEnded, VoiceChatParticipantsInvited, User
@pytest.fixture(scope='class')
def user1():
return User(first_name='Misses Test', id=123, is_bot=False)
@pytest.fixture(scope='class')
def user2():
return User(first_name='Mister Test', id=124, is_bot=False)
class TestVoiceChatStarted:
def test_de_json(self):
voice_chat_started = VoiceChatStarted.de_json({}, None)
assert isinstance(voice_chat_started, VoiceChatStarted)
def test_to_dict(self):
voice_chat_started = VoiceChatStarted()
voice_chat_dict = voice_chat_started.to_dict()
assert voice_chat_dict == {}
class TestVoiceChatEnded:
duration = 100
def test_de_json(self):
json_dict = {'duration': self.duration}
voice_chat_ended = VoiceChatEnded.de_json(json_dict, None)
assert voice_chat_ended.duration == self.duration
def test_to_dict(self):
voice_chat_ended = VoiceChatEnded(self.duration)
voice_chat_dict = voice_chat_ended.to_dict()
assert isinstance(voice_chat_dict, dict)
assert voice_chat_dict["duration"] == self.duration
def test_equality(self):
a = VoiceChatEnded(100)
b = VoiceChatEnded(100)
c = VoiceChatEnded(50)
d = VoiceChatStarted()
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
class TestVoiceChatParticipantsInvited:
def test_de_json(self, user1, user2, bot):
json_data = {"users": [user1.to_dict(), user2.to_dict()]}
voice_chat_participants = VoiceChatParticipantsInvited.de_json(json_data, bot)
assert isinstance(voice_chat_participants.users, list)
assert voice_chat_participants.users[0] == user1
assert voice_chat_participants.users[1] == user2
assert voice_chat_participants.users[0].id == user1.id
assert voice_chat_participants.users[1].id == user2.id
def test_to_dict(self, user1, user2):
voice_chat_participants = VoiceChatParticipantsInvited([user1, user2])
voice_chat_dict = voice_chat_participants.to_dict()
assert isinstance(voice_chat_dict, dict)
assert voice_chat_dict["users"] == [user1.to_dict(), user2.to_dict()]
assert voice_chat_dict["users"][0]["id"] == user1.id
assert voice_chat_dict["users"][1]["id"] == user2.id
def test_equality(self, user1, user2):
a = VoiceChatParticipantsInvited([user1])
b = VoiceChatParticipantsInvited([user1])
c = VoiceChatParticipantsInvited([user1, user2])
d = VoiceChatParticipantsInvited([user2])
e = VoiceChatStarted()
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)