mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-11-24 16:17:37 +01:00
API 4.7 (#1858)
* Pure API changes * Address review * set Bot.commands on successfull call of set_my_commands * Get started on tests * More tests! * More Coverage! * Reset changes in utils.request * Filters.dice, Filters.dice.text * more coverage * Address review * Address review * Test stop_poll with reply_markup * Test stop_poll also without reply_markup * Rephrase note on 'dice' * Fix grammar in note on Filters.dice * update api version readme * address review
This commit is contained in:
parent
f379f54d5a
commit
d63e710784
19 changed files with 686 additions and 47 deletions
|
@ -93,7 +93,7 @@ make the development of bots easy and straightforward. These classes are contain
|
|||
Telegram API support
|
||||
====================
|
||||
|
||||
All types and methods of the Telegram Bot API **4.6** are supported.
|
||||
All types and methods of the Telegram Bot API **4.7** are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
|
|
6
docs/source/telegram.botcommand.rst
Normal file
6
docs/source/telegram.botcommand.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
telegram.BotCommand
|
||||
===================
|
||||
|
||||
.. autoclass:: telegram.BotCommand
|
||||
:members:
|
||||
:show-inheritance:
|
6
docs/source/telegram.dice.rst
Normal file
6
docs/source/telegram.dice.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
telegram.Dice
|
||||
=============
|
||||
|
||||
.. autoclass:: telegram.Dice
|
||||
:members:
|
||||
:show-inheritance:
|
|
@ -9,6 +9,7 @@ telegram package
|
|||
telegram.animation
|
||||
telegram.audio
|
||||
telegram.bot
|
||||
telegram.botcommand
|
||||
telegram.callbackquery
|
||||
telegram.chat
|
||||
telegram.chataction
|
||||
|
@ -17,6 +18,7 @@ telegram package
|
|||
telegram.chatphoto
|
||||
telegram.constants
|
||||
telegram.contact
|
||||
telegram.dice
|
||||
telegram.document
|
||||
telegram.error
|
||||
telegram.file
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"""A library that provides a Python interface to the Telegram Bot API"""
|
||||
|
||||
from .base import TelegramObject
|
||||
from .botcommand import BotCommand
|
||||
from .user import User
|
||||
from .files.chatphoto import ChatPhoto
|
||||
from .chat import Chat
|
||||
|
@ -36,6 +37,7 @@ from .files.location import Location
|
|||
from .files.venue import Venue
|
||||
from .files.videonote import VideoNote
|
||||
from .chataction import ChatAction
|
||||
from .dice import Dice
|
||||
from .userprofilephotos import UserProfilePhotos
|
||||
from .keyboardbutton import KeyboardButton
|
||||
from .keyboardbuttonpolltype import KeyboardButtonPollType
|
||||
|
@ -157,5 +159,6 @@ __all__ = [
|
|||
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
|
||||
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
|
||||
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
|
||||
'PollOption', 'PollAnswer', 'LoginUrl', 'KeyboardButton', 'KeyboardButtonPollType',
|
||||
'PollOption', 'PollAnswer', 'LoginUrl', 'KeyboardButton', 'KeyboardButtonPollType', 'Dice',
|
||||
'BotCommand'
|
||||
]
|
||||
|
|
247
telegram/bot.py
247
telegram/bot.py
|
@ -39,7 +39,7 @@ from future.utils import string_types
|
|||
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
|
||||
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet,
|
||||
PhotoSize, Audio, Document, Sticker, Video, Animation, Voice, VideoNote,
|
||||
Location, Venue, Contact, InputFile, Poll)
|
||||
Location, Venue, Contact, InputFile, Poll, BotCommand)
|
||||
from telegram.error import InvalidToken, TelegramError
|
||||
from telegram.utils.helpers import to_timestamp, DEFAULT_NONE
|
||||
from telegram.utils.request import Request
|
||||
|
@ -53,6 +53,9 @@ def info(func):
|
|||
if not self.bot:
|
||||
self.get_me()
|
||||
|
||||
if self._commands is None:
|
||||
self.get_my_commands()
|
||||
|
||||
result = func(self, *args, **kwargs)
|
||||
return result
|
||||
|
||||
|
@ -141,6 +144,7 @@ class Bot(TelegramObject):
|
|||
self.base_url = str(base_url) + str(self.token)
|
||||
self.base_file_url = str(base_file_url) + str(self.token)
|
||||
self.bot = None
|
||||
self._commands = None
|
||||
self._request = request or Request()
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -159,7 +163,7 @@ class Bot(TelegramObject):
|
|||
|
||||
if reply_markup is not None:
|
||||
if isinstance(reply_markup, ReplyMarkup):
|
||||
data['reply_markup'] = reply_markup.to_json()
|
||||
data['reply_markup'] = reply_markup.to_dict()
|
||||
else:
|
||||
data['reply_markup'] = reply_markup
|
||||
|
||||
|
@ -251,6 +255,13 @@ class Bot(TelegramObject):
|
|||
|
||||
return self.bot.supports_inline_queries
|
||||
|
||||
@property
|
||||
@info
|
||||
def commands(self):
|
||||
"""List[:class:`BotCommand`]: Bot's commands."""
|
||||
|
||||
return self._commands
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""":obj:`str`: Bot's @username."""
|
||||
|
@ -344,6 +355,8 @@ class Bot(TelegramObject):
|
|||
limitations:
|
||||
|
||||
- A message can only be deleted if it was sent less than 48 hours ago.
|
||||
- A dice message in a private chat can only be deleted if it was sent more than 24
|
||||
hours ago.
|
||||
- Bots can delete outgoing messages in private chats, groups, and supergroups.
|
||||
- Bots can delete incoming messages in private chats.
|
||||
- Bots granted can_post_messages permissions can delete outgoing messages in channels.
|
||||
|
@ -3308,15 +3321,23 @@ class Bot(TelegramObject):
|
|||
return File.de_json(result, self)
|
||||
|
||||
@log
|
||||
def create_new_sticker_set(self, user_id, name, title, png_sticker, emojis,
|
||||
contains_masks=None, mask_position=None, timeout=20, **kwargs):
|
||||
def create_new_sticker_set(self, user_id, name, title, emojis, png_sticker=None,
|
||||
contains_masks=None, mask_position=None, timeout=20,
|
||||
tgs_sticker=None, **kwargs):
|
||||
"""Use this method to create new sticker set owned by a user.
|
||||
|
||||
The bot will be able to edit the created sticker set.
|
||||
|
||||
You must use exactly one of the fields :attr:`png_sticker` or :attr:`tgs_sticker`.
|
||||
|
||||
Warning:
|
||||
As of API 4.7 ``png_sticker`` is an optional argument and therefore the order of the
|
||||
arguments had to be changed. Use keyword arguments to make sure that the arguments are
|
||||
passed correctly.
|
||||
|
||||
Note:
|
||||
The png_sticker argument can be either a file_id, an URL or a file from disk
|
||||
``open(filename, 'rb')``
|
||||
The png_sticker and tgs_sticker argument can be either a file_id, an URL or a file from
|
||||
disk ``open(filename, 'rb')``
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): User identifier of created sticker set owner.
|
||||
|
@ -3326,12 +3347,16 @@ class Bot(TelegramObject):
|
|||
must end in "_by_<bot username>". <bot_username> is case insensitive.
|
||||
1-64 characters.
|
||||
title (:obj:`str`): Sticker set title, 1-64 characters.
|
||||
png_sticker (:obj:`str` | `filelike object`): Png image with the sticker, must be up
|
||||
to 512 kilobytes in size, dimensions must not exceed 512px,
|
||||
png_sticker (:obj:`str` | `filelike object`, optional): Png image with the sticker,
|
||||
must be up to 512 kilobytes in size, dimensions must not exceed 512px,
|
||||
and either width or height must be exactly 512px. Pass a file_id as a String to
|
||||
send a file that already exists on the Telegram servers, pass an HTTP URL as a
|
||||
String for Telegram to get a file from the Internet, or upload a new one
|
||||
using multipart/form-data.
|
||||
tgs_sticker (:obj:`str` | `filelike object`, optional): TGS animation with the sticker,
|
||||
uploaded using multipart/form-data. See
|
||||
https://core.telegram.org/animated_stickers#technical-requirements for technical
|
||||
requirements
|
||||
emojis (:obj:`str`): One or more emoji corresponding to the sticker.
|
||||
contains_masks (:obj:`bool`, optional): Pass True, if a set of mask stickers should be
|
||||
created.
|
||||
|
@ -3354,13 +3379,19 @@ class Bot(TelegramObject):
|
|||
if InputFile.is_file(png_sticker):
|
||||
png_sticker = InputFile(png_sticker)
|
||||
|
||||
data = {'user_id': user_id, 'name': name, 'title': title, 'png_sticker': png_sticker,
|
||||
'emojis': emojis}
|
||||
if InputFile.is_file(tgs_sticker):
|
||||
tgs_sticker = InputFile(tgs_sticker)
|
||||
|
||||
data = {'user_id': user_id, 'name': name, 'title': title, 'emojis': emojis}
|
||||
|
||||
if png_sticker is not None:
|
||||
data['png_sticker'] = png_sticker
|
||||
if tgs_sticker is not None:
|
||||
data['tgs_sticker'] = tgs_sticker
|
||||
if contains_masks is not None:
|
||||
data['contains_masks'] = contains_masks
|
||||
if mask_position is not None:
|
||||
data['mask_position'] = mask_position
|
||||
data['mask_position'] = mask_position.to_json()
|
||||
data.update(kwargs)
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
@ -3368,23 +3399,35 @@ class Bot(TelegramObject):
|
|||
return result
|
||||
|
||||
@log
|
||||
def add_sticker_to_set(self, user_id, name, png_sticker, emojis, mask_position=None,
|
||||
timeout=20, **kwargs):
|
||||
"""Use this method to add a new sticker to a set created by the bot.
|
||||
def add_sticker_to_set(self, user_id, name, emojis, png_sticker=None, mask_position=None,
|
||||
timeout=20, tgs_sticker=None, **kwargs):
|
||||
"""Use this method to add a new sticker to a set created by the bot. You must use exactly
|
||||
one of the fields png_sticker or tgs_sticker. Animated stickers can be added to animated
|
||||
sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static
|
||||
sticker sets can have up to 120 stickers.
|
||||
|
||||
Warning:
|
||||
As of API 4.7 ``png_sticker`` is an optional argument and therefore the order of the
|
||||
arguments had to be changed. Use keyword arguments to make sure that the arguments are
|
||||
passed correctly.
|
||||
|
||||
Note:
|
||||
The png_sticker argument can be either a file_id, an URL or a file from disk
|
||||
``open(filename, 'rb')``
|
||||
The png_sticker and tgs_sticker argument can be either a file_id, an URL or a file from
|
||||
disk ``open(filename, 'rb')``
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): User identifier of created sticker set owner.
|
||||
name (:obj:`str`): Sticker set name.
|
||||
png_sticker (:obj:`str` | `filelike object`): Png image with the sticker, must be up
|
||||
to 512 kilobytes in size, dimensions must not exceed 512px,
|
||||
png_sticker (:obj:`str` | `filelike object`, optional): Png image with the sticker,
|
||||
must be up to 512 kilobytes in size, dimensions must not exceed 512px,
|
||||
and either width or height must be exactly 512px. Pass a file_id as a String to
|
||||
send a file that already exists on the Telegram servers, pass an HTTP URL as a
|
||||
String for Telegram to get a file from the Internet, or upload a new one
|
||||
using multipart/form-data.
|
||||
tgs_sticker (:obj:`str` | `filelike object`, optional): TGS animation with the sticker,
|
||||
uploaded using multipart/form-data. See
|
||||
https://core.telegram.org/animated_stickers#technical-requirements for technical
|
||||
requirements
|
||||
emojis (:obj:`str`): One or more emoji corresponding to the sticker.
|
||||
mask_position (:class:`telegram.MaskPosition`, optional): Position where the mask
|
||||
should beplaced on faces.
|
||||
|
@ -3405,10 +3448,17 @@ class Bot(TelegramObject):
|
|||
if InputFile.is_file(png_sticker):
|
||||
png_sticker = InputFile(png_sticker)
|
||||
|
||||
data = {'user_id': user_id, 'name': name, 'png_sticker': png_sticker, 'emojis': emojis}
|
||||
if InputFile.is_file(tgs_sticker):
|
||||
tgs_sticker = InputFile(tgs_sticker)
|
||||
|
||||
data = {'user_id': user_id, 'name': name, 'emojis': emojis}
|
||||
|
||||
if png_sticker is not None:
|
||||
data['png_sticker'] = png_sticker
|
||||
if tgs_sticker is not None:
|
||||
data['tgs_sticker'] = tgs_sticker
|
||||
if mask_position is not None:
|
||||
data['mask_position'] = mask_position
|
||||
data['mask_position'] = mask_position.to_json()
|
||||
data.update(kwargs)
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
@ -3470,6 +3520,48 @@ class Bot(TelegramObject):
|
|||
|
||||
return result
|
||||
|
||||
@log
|
||||
def set_sticker_set_thumb(self, name, user_id, thumb=None, timeout=None, **kwargs):
|
||||
"""Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set
|
||||
for animated sticker sets only.
|
||||
|
||||
Note:
|
||||
The thumb can be either a file_id, an URL or a file from disk ``open(filename, 'rb')``
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): Sticker set name
|
||||
user_id (:obj:`int`): User identifier of created sticker set owner.
|
||||
thumb (:obj:`str` | `filelike object`, optional): A PNG image with the thumbnail, must
|
||||
be up to 128 kilobytes in size and have width and height exactly 100px, or a TGS
|
||||
animation with the thumbnail up to 32 kilobytes in size; see
|
||||
https://core.telegram.org/animated_stickers#technical-requirements for animated sticker
|
||||
technical requirements. Pass a file_id as a String to send a file that already exists
|
||||
on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from
|
||||
the Internet, or upload a new one using multipart/form-data.
|
||||
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).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, ``True`` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
url = '{}/setStickerSetThumb'.format(self.base_url)
|
||||
|
||||
if InputFile.is_file(thumb):
|
||||
thumb = InputFile(thumb)
|
||||
|
||||
data = {'name': name, 'user_id': user_id, 'thumb': thumb}
|
||||
data.update(kwargs)
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
||||
return result
|
||||
|
||||
@log
|
||||
def set_passport_data_errors(self, user_id, errors, timeout=None, **kwargs):
|
||||
"""
|
||||
|
@ -3621,7 +3713,7 @@ class Bot(TelegramObject):
|
|||
|
||||
if reply_markup:
|
||||
if isinstance(reply_markup, ReplyMarkup):
|
||||
data['reply_markup'] = reply_markup.to_json()
|
||||
data['reply_markup'] = reply_markup.to_dict()
|
||||
else:
|
||||
data['reply_markup'] = reply_markup
|
||||
|
||||
|
@ -3629,6 +3721,111 @@ class Bot(TelegramObject):
|
|||
|
||||
return Poll.de_json(result, self)
|
||||
|
||||
@log
|
||||
def send_dice(self,
|
||||
chat_id,
|
||||
disable_notification=None,
|
||||
reply_to_message_id=None,
|
||||
reply_markup=None,
|
||||
timeout=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Use this method to send a dice, which will have a random value from 1 to 6. On success, the
|
||||
sent Message is returned.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target private chat.
|
||||
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
|
||||
receive a notification with no sound.
|
||||
reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the
|
||||
original message.
|
||||
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
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).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, the sent Message is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
url = '{0}/sendDice'.format(self.base_url)
|
||||
|
||||
data = {
|
||||
'chat_id': chat_id,
|
||||
}
|
||||
|
||||
return self._message(url, data, timeout=timeout, disable_notification=disable_notification,
|
||||
reply_to_message_id=reply_to_message_id, reply_markup=reply_markup,
|
||||
**kwargs)
|
||||
|
||||
@log
|
||||
def get_my_commands(self, timeout=None, **kwargs):
|
||||
"""
|
||||
Use this method to get the current list of the bot's commands.
|
||||
|
||||
Args:
|
||||
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).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
List[:class:`telegram.BotCommand]`: On success, the commands set for the bot
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
url = '{0}/getMyCommands'.format(self.base_url)
|
||||
|
||||
result = self._request.get(url, timeout=timeout)
|
||||
|
||||
self._commands = [BotCommand.de_json(c, self) for c in result]
|
||||
|
||||
return self._commands
|
||||
|
||||
@log
|
||||
def set_my_commands(self, commands, timeout=None, **kwargs):
|
||||
"""
|
||||
Use this method to change the list of the bot's commands.
|
||||
|
||||
Args:
|
||||
commands (List[:class:`BotCommand` | (:obj:`str`, :obj:`str`)]): A JSON-serialized list
|
||||
of bot commands to be set as the list of the bot's commands. At most 100 commands
|
||||
can be specified.
|
||||
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).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
:obj:`True`: On success
|
||||
|
||||
Raises:
|
||||
:class:`telegram.TelegramError`
|
||||
|
||||
"""
|
||||
url = '{0}/setMyCommands'.format(self.base_url)
|
||||
|
||||
cmds = [c if isinstance(c, BotCommand) else BotCommand(c[0], c[1]) for c in commands]
|
||||
|
||||
data = {'commands': [c.to_dict() for c in cmds]}
|
||||
data.update(kwargs)
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
||||
# Set commands. No need to check for outcome.
|
||||
# If request failed, we won't come this far
|
||||
self._commands = commands
|
||||
|
||||
return result
|
||||
|
||||
def to_dict(self):
|
||||
data = {'id': self.id, 'username': self.username, 'first_name': self.first_name}
|
||||
|
||||
|
@ -3768,9 +3965,17 @@ class Bot(TelegramObject):
|
|||
"""Alias for :attr:`set_sticker_position_in_set`"""
|
||||
deleteStickerFromSet = delete_sticker_from_set
|
||||
"""Alias for :attr:`delete_sticker_from_set`"""
|
||||
setStickerSetThumb = set_sticker_set_thumb
|
||||
"""Alias for :attr:`set_sticker_set_thumb`"""
|
||||
setPassportDataErrors = set_passport_data_errors
|
||||
"""Alias for :attr:`set_passport_data_errors`"""
|
||||
sendPoll = send_poll
|
||||
"""Alias for :attr:`send_poll`"""
|
||||
stopPoll = stop_poll
|
||||
"""Alias for :attr:`stop_poll`"""
|
||||
sendDice = send_dice
|
||||
"""Alias for :attr:`send_dice`"""
|
||||
getMyCommands = get_my_commands
|
||||
"""Alias for :attr:`get_my_commands`"""
|
||||
setMyCommands = set_my_commands
|
||||
"""Alias for :attr:`set_my_commands`"""
|
||||
|
|
46
telegram/botcommand.py
Normal file
46
telegram/botcommand.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python
|
||||
# pylint: disable=R0903
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2020
|
||||
# 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 Bot Command."""
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
class BotCommand(TelegramObject):
|
||||
"""
|
||||
This object represents a bot command.
|
||||
|
||||
Attributes:
|
||||
command (:obj:`str`): Text of the command.
|
||||
description (:obj:`str`): Description of the command.
|
||||
|
||||
Args:
|
||||
command (:obj:`str`): Text of the command, 1-32 characters. Can contain only lowercase
|
||||
English letters, digits and underscores.
|
||||
description (:obj:`str`): Description of the command, 3-256 characters.
|
||||
"""
|
||||
def __init__(self, command, description, **kwargs):
|
||||
self.command = command
|
||||
self.description = description
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
return cls(**data)
|
43
telegram/dice.py
Normal file
43
telegram/dice.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python
|
||||
# pylint: disable=R0903
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2020
|
||||
# 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 Dice."""
|
||||
from telegram import TelegramObject
|
||||
|
||||
|
||||
class Dice(TelegramObject):
|
||||
"""
|
||||
This object represents a dice with random value from 1 to 6. (The singular form of "dice" is
|
||||
"die". However, PTB mimics the Telegram API, which uses the term "dice".)
|
||||
|
||||
Attributes:
|
||||
value (:obj:`int`): Value of the dice.
|
||||
|
||||
Args:
|
||||
value (:obj:`int`): Value of the dice, 1-6.
|
||||
"""
|
||||
def __init__(self, value, **kwargs):
|
||||
self.value = value
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data, bot):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
return cls(**data)
|
|
@ -272,6 +272,10 @@ class Filters(object):
|
|||
...
|
||||
MessageHandler(Filters.text(buttons), callback_method)
|
||||
|
||||
Note:
|
||||
Dice messages don't have text. If you want to filter either text or dice messages, use
|
||||
``Filters.text | Filters.dice``.
|
||||
|
||||
Args:
|
||||
update (List[:obj:`str`] | Tuple[:obj:`str`], optional): Which messages to allow. Only
|
||||
exact matches are allowed. If not specified, will allow any text message.
|
||||
|
@ -427,7 +431,7 @@ class Filters(object):
|
|||
send media with wrong types that don't fit to this handler.
|
||||
|
||||
Example:
|
||||
Filters.documents.category('audio/') returnes `True` for all types
|
||||
Filters.documents.category('audio/') returns `True` for all types
|
||||
of audio sent as file, for example 'audio/mpeg' or 'audio/x-wav'
|
||||
"""
|
||||
|
||||
|
@ -957,6 +961,48 @@ officedocument.wordprocessingml.document")``-
|
|||
poll = _Poll()
|
||||
"""Messages that contain a :class:`telegram.Poll`."""
|
||||
|
||||
class _Dice(BaseFilter):
|
||||
name = 'Filters.dice'
|
||||
|
||||
class _DiceValues(BaseFilter):
|
||||
|
||||
def __init__(self, values):
|
||||
self.values = [values] if isinstance(values, int) else values
|
||||
self.name = 'Filters.dice({})'.format(values)
|
||||
|
||||
def filter(self, message):
|
||||
return bool(message.dice and message.dice.value in self.values)
|
||||
|
||||
def __call__(self, update):
|
||||
if isinstance(update, Update):
|
||||
return self.filter(update.effective_message)
|
||||
else:
|
||||
return self._DiceValues(update)
|
||||
|
||||
def filter(self, message):
|
||||
return bool(message.dice)
|
||||
|
||||
dice = _Dice()
|
||||
"""Dice Messages. If an integer or a list of integers is passed, it filters messages to only
|
||||
allow those whose dice value is appearing in the given list.
|
||||
|
||||
Examples:
|
||||
To allow any dice message, simply use
|
||||
``MessageHandler(Filters.dice, callback_method)``.
|
||||
To allow only dice with value 6, use
|
||||
``MessageHandler(Filters.dice(6), callback_method)``.
|
||||
To allow only dice with value 5 `or` 6, use
|
||||
``MessageHandler(Filters.dice([5, 6]), callback_method)``.
|
||||
|
||||
Args:
|
||||
update (:obj:`int` | List[:obj:`int`], optional): Which values to allow. If not
|
||||
specified, will allow any dice message.
|
||||
|
||||
Note:
|
||||
Dice messages don't have text. If you want to filter either text or dice messages, use
|
||||
``Filters.text | Filters.dice``.
|
||||
"""
|
||||
|
||||
class language(BaseFilter):
|
||||
"""Filters messages to only allow those which are from users with a certain language code.
|
||||
|
||||
|
|
|
@ -138,6 +138,8 @@ class StickerSet(TelegramObject):
|
|||
is_animated (:obj:`bool`): True, if the sticker set contains animated stickers.
|
||||
contains_masks (:obj:`bool`): True, if the sticker set contains masks.
|
||||
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
|
||||
thumb (:class:`telegram.PhotoSize`): Optional. Sticker set thumbnail in the .WEBP or .TGS
|
||||
format
|
||||
|
||||
Args:
|
||||
name (:obj:`str`): Sticker set name.
|
||||
|
@ -145,15 +147,20 @@ class StickerSet(TelegramObject):
|
|||
is_animated (:obj:`bool`): True, if the sticker set contains animated stickers.
|
||||
contains_masks (:obj:`bool`): True, if the sticker set contains masks.
|
||||
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
|
||||
thumb (:class:`telegram.PhotoSize`, optional): Sticker set thumbnail in the .WEBP or .TGS
|
||||
format
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, title, is_animated, contains_masks, stickers, bot=None, **kwargs):
|
||||
def __init__(self, name, title, is_animated, contains_masks, stickers, bot=None, thumb=None,
|
||||
**kwargs):
|
||||
self.name = name
|
||||
self.title = title
|
||||
self.is_animated = is_animated
|
||||
self.contains_masks = contains_masks
|
||||
self.stickers = stickers
|
||||
# Optionals
|
||||
self.thumb = thumb
|
||||
|
||||
self._id_attrs = (self.name,)
|
||||
|
||||
|
@ -164,6 +171,7 @@ class StickerSet(TelegramObject):
|
|||
|
||||
data = super(StickerSet, StickerSet).de_json(data, bot)
|
||||
|
||||
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
|
||||
data['stickers'] = Sticker.de_list(data.get('stickers'), bot)
|
||||
|
||||
return StickerSet(bot=bot, **data)
|
||||
|
|
|
@ -23,7 +23,7 @@ from html import escape
|
|||
|
||||
from telegram import (Animation, Audio, Contact, Document, Chat, Location, PhotoSize, Sticker,
|
||||
TelegramObject, User, Video, Voice, Venue, MessageEntity, Game, Invoice,
|
||||
SuccessfulPayment, VideoNote, PassportData, Poll, InlineKeyboardMarkup)
|
||||
SuccessfulPayment, VideoNote, PassportData, Poll, InlineKeyboardMarkup, Dice)
|
||||
from telegram import ParseMode
|
||||
from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp
|
||||
|
||||
|
@ -106,6 +106,7 @@ class Message(TelegramObject):
|
|||
passport_data (:class:`telegram.PassportData`): Optional. Telegram Passport data.
|
||||
poll (:class:`telegram.Poll`): Optional. Message is a native poll,
|
||||
information about the poll.
|
||||
dice (:class:`telegram.Dice`): Optional. Message is a dice.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
||||
|
@ -199,7 +200,7 @@ class Message(TelegramObject):
|
|||
smaller than 52 bits, so a signed 64 bit integer or double-precision float type are
|
||||
safe for storing this identifier.
|
||||
pinned_message (:class:`telegram.message`, optional): Specified message was pinned. Note
|
||||
that the Message object in this field will not contain further attr:`reply_to_message`
|
||||
that the Message object in this field will not contain further :attr:`reply_to_message`
|
||||
fields even if it is itself a reply.
|
||||
invoice (:class:`telegram.Invoice`, optional): Message is an invoice for a payment,
|
||||
information about the invoice.
|
||||
|
@ -214,6 +215,7 @@ class Message(TelegramObject):
|
|||
passport_data (:class:`telegram.PassportData`, optional): Telegram Passport data.
|
||||
poll (:class:`telegram.Poll`, optional): Message is a native poll,
|
||||
information about the poll.
|
||||
dice (:class:`telegram.Dice`, optional): Message is a dice with random value from 1 to 6.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message. login_url buttons are represented as ordinary url buttons.
|
||||
default_quote (:obj:`bool`, optional): Default setting for the `quote` parameter of the
|
||||
|
@ -229,7 +231,7 @@ class Message(TelegramObject):
|
|||
MESSAGE_TYPES = ['text', 'new_chat_members', 'left_chat_member', 'new_chat_title',
|
||||
'new_chat_photo', 'delete_chat_photo', 'group_chat_created',
|
||||
'supergroup_chat_created', 'channel_chat_created', 'migrate_to_chat_id',
|
||||
'migrate_from_chat_id', 'pinned_message',
|
||||
'migrate_from_chat_id', 'pinned_message', 'poll', 'dice',
|
||||
'passport_data'] + ATTACHMENT_TYPES
|
||||
|
||||
def __init__(self,
|
||||
|
@ -282,6 +284,7 @@ class Message(TelegramObject):
|
|||
reply_markup=None,
|
||||
bot=None,
|
||||
default_quote=None,
|
||||
dice=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.message_id = int(message_id)
|
||||
|
@ -331,6 +334,7 @@ class Message(TelegramObject):
|
|||
self.animation = animation
|
||||
self.passport_data = passport_data
|
||||
self.poll = poll
|
||||
self.dice = dice
|
||||
self.reply_markup = reply_markup
|
||||
self.bot = bot
|
||||
self.default_quote = default_quote
|
||||
|
@ -404,6 +408,7 @@ class Message(TelegramObject):
|
|||
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot)
|
||||
data['passport_data'] = PassportData.de_json(data.get('passport_data'), bot)
|
||||
data['poll'] = Poll.de_json(data.get('poll'), bot)
|
||||
data['dice'] = Dice.de_json(data.get('dice'), bot)
|
||||
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
|
||||
|
||||
return cls(bot=bot, **data)
|
||||
|
@ -808,6 +813,23 @@ class Message(TelegramObject):
|
|||
self._quote(kwargs)
|
||||
return self.bot.send_poll(self.chat_id, *args, **kwargs)
|
||||
|
||||
def reply_dice(self, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
bot.send_dice(update.message.chat_id, *args, **kwargs)
|
||||
|
||||
Keyword Args:
|
||||
quote (:obj:`bool`, optional): If set to ``True``, the dice is sent as an actual reply
|
||||
to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
|
||||
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, instance representing the message posted.
|
||||
|
||||
"""
|
||||
self._quote(kwargs)
|
||||
return self.bot.send_dice(self.chat_id, *args, **kwargs)
|
||||
|
||||
def forward(self, chat_id, *args, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
|
|
BIN
tests/data/sticker_set_thumb.png
Normal file
BIN
tests/data/sticker_set_thumb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
tests/data/telegram_animated_sticker.tgs
Normal file
BIN
tests/data/telegram_animated_sticker.tgs
Normal file
Binary file not shown.
|
@ -26,7 +26,7 @@ from future.utils import string_types
|
|||
|
||||
from telegram import (Bot, Update, ChatAction, TelegramError, User, InlineKeyboardMarkup,
|
||||
InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent,
|
||||
ShippingOption, LabeledPrice, ChatPermissions, Poll,
|
||||
ShippingOption, LabeledPrice, ChatPermissions, Poll, BotCommand,
|
||||
InlineQueryResultDocument)
|
||||
from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter
|
||||
from telegram.utils.helpers import from_timestamp, escape_markdown
|
||||
|
@ -80,6 +80,7 @@ class TestBot(object):
|
|||
@pytest.mark.timeout(10)
|
||||
def test_get_me_and_properties(self, bot):
|
||||
get_me_bot = bot.get_me()
|
||||
commands = bot.get_my_commands()
|
||||
|
||||
assert isinstance(get_me_bot, User)
|
||||
assert get_me_bot.id == bot.id
|
||||
|
@ -91,6 +92,7 @@ class TestBot(object):
|
|||
assert get_me_bot.can_read_all_group_messages == bot.can_read_all_group_messages
|
||||
assert get_me_bot.supports_inline_queries == bot.supports_inline_queries
|
||||
assert 'https://t.me/{}'.format(get_me_bot.username) == bot.link
|
||||
assert commands == bot.commands
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
|
@ -174,7 +176,13 @@ class TestBot(object):
|
|||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_and_stop_poll(self, bot, super_group_id):
|
||||
@pytest.mark.parametrize('reply_markup', [
|
||||
None,
|
||||
InlineKeyboardMarkup.from_button(InlineKeyboardButton(text='text', callback_data='data')),
|
||||
InlineKeyboardMarkup.from_button(
|
||||
InlineKeyboardButton(text='text', callback_data='data')).to_dict()
|
||||
])
|
||||
def test_send_and_stop_poll(self, bot, super_group_id, reply_markup):
|
||||
question = 'Is this a test?'
|
||||
answers = ['Yes', 'No', 'Maybe']
|
||||
message = bot.send_poll(chat_id=super_group_id, question=question, options=answers,
|
||||
|
@ -190,7 +198,10 @@ class TestBot(object):
|
|||
assert not message.poll.is_closed
|
||||
assert message.poll.type == Poll.REGULAR
|
||||
|
||||
poll = bot.stop_poll(chat_id=super_group_id, message_id=message.message_id, timeout=60)
|
||||
# Since only the poll and not the complete message is returned, we can't check that the
|
||||
# reply_markup is correct. So we just test that sending doesn't give an error.
|
||||
poll = bot.stop_poll(chat_id=super_group_id, message_id=message.message_id,
|
||||
reply_markup=reply_markup, timeout=60)
|
||||
assert isinstance(poll, Poll)
|
||||
assert poll.is_closed
|
||||
assert poll.options[0].text == answers[0]
|
||||
|
@ -208,6 +219,13 @@ class TestBot(object):
|
|||
assert message_quiz.poll.type == Poll.QUIZ
|
||||
assert message_quiz.poll.is_closed
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_dice(self, bot, chat_id):
|
||||
message = bot.send_dice(chat_id)
|
||||
|
||||
assert message.dice
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_chat_action(self, bot, chat_id):
|
||||
|
@ -904,3 +922,41 @@ class TestBot(object):
|
|||
def test_send_message_default_quote(self, default_bot, chat_id):
|
||||
message = default_bot.send_message(chat_id, 'test')
|
||||
assert message.default_quote is True
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_set_and_get_my_commands(self, bot):
|
||||
commands = [
|
||||
BotCommand('cmd1', 'descr1'),
|
||||
BotCommand('cmd2', 'descr2'),
|
||||
]
|
||||
bot.set_my_commands([])
|
||||
assert bot.get_my_commands() == []
|
||||
assert bot.commands == []
|
||||
assert bot.set_my_commands(commands)
|
||||
|
||||
for bc in [bot.get_my_commands(), bot.commands]:
|
||||
assert len(bc) == 2
|
||||
assert bc[0].command == 'cmd1'
|
||||
assert bc[0].description == 'descr1'
|
||||
assert bc[1].command == 'cmd2'
|
||||
assert bc[1].description == 'descr2'
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_set_and_get_my_commands_strings(self, bot):
|
||||
commands = [
|
||||
['cmd1', 'descr1'],
|
||||
['cmd2', 'descr2'],
|
||||
]
|
||||
bot.set_my_commands([])
|
||||
assert bot.get_my_commands() == []
|
||||
assert bot.commands == []
|
||||
assert bot.set_my_commands(commands)
|
||||
|
||||
for bc in [bot.get_my_commands(), bot.commands]:
|
||||
assert len(bc) == 2
|
||||
assert bc[0].command == 'cmd1'
|
||||
assert bc[0].description == 'descr1'
|
||||
assert bc[1].command == 'cmd2'
|
||||
assert bc[1].description == 'descr2'
|
||||
|
|
48
tests/test_botcommand.py
Normal file
48
tests/test_botcommand.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2020
|
||||
# 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 BotCommand
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def bot_command():
|
||||
return BotCommand(command='start', description='A command')
|
||||
|
||||
|
||||
class TestBotCommand(object):
|
||||
command = 'start'
|
||||
description = 'A command'
|
||||
|
||||
def test_de_json(self, bot):
|
||||
json_dict = {'command': self.command, 'description': self.description}
|
||||
bot_command = BotCommand.de_json(json_dict, bot)
|
||||
|
||||
assert bot_command.command == self.command
|
||||
assert bot_command.description == self.description
|
||||
|
||||
assert BotCommand.de_json(None, bot) is None
|
||||
|
||||
def test_to_dict(self, bot_command):
|
||||
bot_command_dict = bot_command.to_dict()
|
||||
|
||||
assert isinstance(bot_command_dict, dict)
|
||||
assert bot_command_dict['command'] == bot_command.command
|
||||
assert bot_command_dict['description'] == bot_command.description
|
44
tests/test_dice.py
Normal file
44
tests/test_dice.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2020
|
||||
# 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 Dice
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def dice():
|
||||
return Dice(value=5)
|
||||
|
||||
|
||||
class TestDice(object):
|
||||
value = 4
|
||||
|
||||
def test_de_json(self, bot):
|
||||
json_dict = {'value': self.value}
|
||||
dice = Dice.de_json(json_dict, bot)
|
||||
|
||||
assert dice.value == self.value
|
||||
assert Dice.de_json(None, bot) is None
|
||||
|
||||
def test_to_dict(self, dice):
|
||||
dice_dict = dice.to_dict()
|
||||
|
||||
assert isinstance(dice_dict, dict)
|
||||
assert dice_dict['value'] == dice.value
|
|
@ -20,7 +20,7 @@ import datetime
|
|||
|
||||
import pytest
|
||||
|
||||
from telegram import Message, User, Chat, MessageEntity, Document, Update
|
||||
from telegram import Message, User, Chat, MessageEntity, Document, Update, Dice
|
||||
from telegram.ext import Filters, BaseFilter
|
||||
import re
|
||||
|
||||
|
@ -622,6 +622,22 @@ class TestFilters(object):
|
|||
update.message.poll = 'test'
|
||||
assert Filters.poll(update)
|
||||
|
||||
def test_filters_dice(self, update):
|
||||
update.message.dice = Dice(4)
|
||||
assert Filters.dice(update)
|
||||
update.message.dice = None
|
||||
assert not Filters.dice(update)
|
||||
|
||||
def test_filters_dice_iterable(self, update):
|
||||
update.message.dice = None
|
||||
assert not Filters.dice(5)(update)
|
||||
|
||||
update.message.dice = Dice(5)
|
||||
assert Filters.dice(5)(update)
|
||||
assert Filters.dice({5, 6})(update)
|
||||
assert not Filters.dice(1)(update)
|
||||
assert not Filters.dice([2, 3])(update)
|
||||
|
||||
def test_language_filter_single(self, update):
|
||||
update.message.from_user.language_code = 'en_US'
|
||||
assert (Filters.language('en_US'))(update)
|
||||
|
|
|
@ -22,7 +22,7 @@ import pytest
|
|||
|
||||
from telegram import (Update, Message, User, MessageEntity, Chat, Audio, Document, Animation,
|
||||
Game, PhotoSize, Sticker, Video, Voice, VideoNote, Contact, Location, Venue,
|
||||
Invoice, SuccessfulPayment, PassportData, ParseMode, Poll, PollOption)
|
||||
Invoice, SuccessfulPayment, PassportData, ParseMode, Poll, PollOption, Dice)
|
||||
from tests.test_passport import RAW_PASSPORT_DATA
|
||||
|
||||
|
||||
|
@ -97,7 +97,8 @@ def message(bot):
|
|||
'text': 'start', 'url': 'http://google.com'}, {
|
||||
'text': 'next', 'callback_data': 'abcd'}],
|
||||
[{'text': 'Cancel', 'callback_data': 'Cancel'}]]}},
|
||||
{'quote': True}
|
||||
{'quote': True},
|
||||
{'dice': Dice(4)}
|
||||
],
|
||||
ids=['forwarded_user', 'forwarded_channel', 'reply', 'edited', 'text',
|
||||
'caption_entities', 'audio', 'document', 'animation', 'game', 'photo',
|
||||
|
@ -107,7 +108,7 @@ def message(bot):
|
|||
'migrated_from', 'pinned', 'invoice', 'successful_payment',
|
||||
'connected_website', 'forward_signature', 'author_signature',
|
||||
'photo_from_media_group', 'passport_data', 'poll', 'reply_markup',
|
||||
'default_quote'])
|
||||
'default_quote', 'dice'])
|
||||
def message_params(bot, request):
|
||||
return Message(message_id=TestMessage.id_,
|
||||
from_user=TestMessage.from_user,
|
||||
|
@ -702,7 +703,7 @@ class TestMessage(object):
|
|||
def test_reply_poll(self, monkeypatch, message):
|
||||
def test(*args, **kwargs):
|
||||
id_ = args[0] == message.chat_id
|
||||
contact = kwargs['contact'] == 'test_poll'
|
||||
contact = kwargs['question'] == 'test_poll'
|
||||
if kwargs.get('reply_to_message_id'):
|
||||
reply = kwargs['reply_to_message_id'] == message.message_id
|
||||
else:
|
||||
|
@ -710,8 +711,22 @@ class TestMessage(object):
|
|||
return id_ and contact and reply
|
||||
|
||||
monkeypatch.setattr(message.bot, 'send_poll', test)
|
||||
assert message.reply_poll(contact='test_poll')
|
||||
assert message.reply_poll(contact='test_poll', quote=True)
|
||||
assert message.reply_poll(question='test_poll')
|
||||
assert message.reply_poll(question='test_poll', quote=True)
|
||||
|
||||
def test_reply_dice(self, monkeypatch, message):
|
||||
def test(*args, **kwargs):
|
||||
id_ = args[0] == message.chat_id
|
||||
contact = kwargs['disable_notification'] is True
|
||||
if kwargs.get('reply_to_message_id'):
|
||||
reply = kwargs['reply_to_message_id'] == message.message_id
|
||||
else:
|
||||
reply = True
|
||||
return id_ and contact and reply
|
||||
|
||||
monkeypatch.setattr(message.bot, 'send_dice', test)
|
||||
assert message.reply_dice(disable_notification=True)
|
||||
assert message.reply_dice(disable_notification=True, quote=True)
|
||||
|
||||
def test_forward(self, monkeypatch, message):
|
||||
def test(*args, **kwargs):
|
||||
|
|
|
@ -40,6 +40,19 @@ def sticker(bot, chat_id):
|
|||
return bot.send_sticker(chat_id, sticker=f, timeout=50).sticker
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def animated_sticker_file():
|
||||
f = open('tests/data/telegram_animated_sticker.tgs', 'rb')
|
||||
yield f
|
||||
f.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def animated_sticker(bot, chat_id):
|
||||
with open('tests/data/telegram_animated_sticker.tgs', 'rb') as f:
|
||||
return bot.send_sticker(chat_id, sticker=f, timeout=50).sticker
|
||||
|
||||
|
||||
class TestSticker(object):
|
||||
# sticker_file_url = 'https://python-telegram-bot.org/static/testfiles/telegram.webp'
|
||||
# Serving sticker from gh since our server sends wrong content_type
|
||||
|
@ -245,12 +258,27 @@ class TestSticker(object):
|
|||
|
||||
@pytest.fixture(scope='function')
|
||||
def sticker_set(bot):
|
||||
ss = bot.get_sticker_set('test_by_{0}'.format(bot.username))
|
||||
ss = bot.get_sticker_set('test_by_{}'.format(bot.username))
|
||||
if len(ss.stickers) > 100:
|
||||
raise Exception('stickerset is growing too large.')
|
||||
return ss
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def animated_sticker_set(bot):
|
||||
ss = bot.get_sticker_set('animated_test_by_{}'.format(bot.username))
|
||||
if len(ss.stickers) > 100:
|
||||
raise Exception('stickerset is growing too large.')
|
||||
return ss
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def sticker_set_thumb_file():
|
||||
f = open('tests/data/sticker_set_thumb.png', 'rb')
|
||||
yield f
|
||||
f.close()
|
||||
|
||||
|
||||
class TestStickerSet(object):
|
||||
title = 'Test stickers'
|
||||
is_animated = True
|
||||
|
@ -258,14 +286,15 @@ class TestStickerSet(object):
|
|||
stickers = [Sticker('file_id', 'file_un_id', 512, 512, True)]
|
||||
name = 'NOTAREALNAME'
|
||||
|
||||
def test_de_json(self, bot):
|
||||
name = 'test_by_{0}'.format(bot.username)
|
||||
def test_de_json(self, bot, sticker):
|
||||
name = 'test_by_{}'.format(bot.username)
|
||||
json_dict = {
|
||||
'name': name,
|
||||
'title': self.title,
|
||||
'is_animated': self.is_animated,
|
||||
'contains_masks': self.contains_masks,
|
||||
'stickers': [x.to_dict() for x in self.stickers]
|
||||
'stickers': [x.to_dict() for x in self.stickers],
|
||||
'thumb': sticker.thumb.to_dict()
|
||||
}
|
||||
sticker_set = StickerSet.de_json(json_dict, bot)
|
||||
|
||||
|
@ -274,15 +303,28 @@ class TestStickerSet(object):
|
|||
assert sticker_set.is_animated == self.is_animated
|
||||
assert sticker_set.contains_masks == self.contains_masks
|
||||
assert sticker_set.stickers == self.stickers
|
||||
assert sticker_set.thumb == sticker.thumb
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_bot_methods_1(self, bot, chat_id):
|
||||
def test_bot_methods_1_png(self, bot, chat_id, sticker_file):
|
||||
with open('tests/data/telegram_sticker.png', 'rb') as f:
|
||||
file = bot.upload_sticker_file(95205500, f)
|
||||
assert file
|
||||
assert bot.add_sticker_to_set(chat_id, 'test_by_{0}'.format(bot.username),
|
||||
file.file_id, '😄')
|
||||
assert bot.add_sticker_to_set(chat_id, 'test_by_{}'.format(bot.username),
|
||||
png_sticker=file.file_id, emojis='😄')
|
||||
# Also test with file input and mask
|
||||
assert bot.add_sticker_to_set(chat_id, 'test_by_{}'.format(bot.username),
|
||||
png_sticker=sticker_file, emojis='😄',
|
||||
mask_position=MaskPosition(MaskPosition.EYES, -1, 1, 2))
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_bot_methods_1_tgs(self, bot, chat_id):
|
||||
assert bot.add_sticker_to_set(
|
||||
chat_id, 'animated_test_by_{}'.format(bot.username),
|
||||
tgs_sticker=open('tests/data/telegram_animated_sticker.tgs', 'rb'),
|
||||
emojis='😄')
|
||||
|
||||
def test_sticker_set_to_dict(self, sticker_set):
|
||||
sticker_set_dict = sticker_set.to_dict()
|
||||
|
@ -296,17 +338,48 @@ class TestStickerSet(object):
|
|||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_bot_methods_2(self, bot, sticker_set):
|
||||
def test_bot_methods_2_png(self, bot, sticker_set):
|
||||
file_id = sticker_set.stickers[0].file_id
|
||||
assert bot.set_sticker_position_in_set(file_id, 1)
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_bot_methods_2_tgs(self, bot, animated_sticker_set):
|
||||
file_id = animated_sticker_set.stickers[0].file_id
|
||||
assert bot.set_sticker_position_in_set(file_id, 1)
|
||||
|
||||
@flaky(10, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_bot_methods_3(self, bot, sticker_set):
|
||||
def test_bot_methods_3_png(self, bot, chat_id, sticker_set_thumb_file):
|
||||
sleep(1)
|
||||
assert bot.set_sticker_set_thumb('test_by_{}'.format(bot.username), chat_id,
|
||||
sticker_set_thumb_file)
|
||||
|
||||
@flaky(10, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_bot_methods_3_tgs(self, bot, chat_id, animated_sticker_file, animated_sticker_set):
|
||||
sleep(1)
|
||||
assert bot.set_sticker_set_thumb('animated_test_by_{}'.format(bot.username), chat_id,
|
||||
animated_sticker_file)
|
||||
file_id = animated_sticker_set.stickers[-1].file_id
|
||||
# also test with file input and mask
|
||||
assert bot.set_sticker_set_thumb('animated_test_by_{}'.format(bot.username), chat_id,
|
||||
file_id)
|
||||
|
||||
@flaky(10, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_bot_methods_4_png(self, bot, sticker_set):
|
||||
sleep(1)
|
||||
file_id = sticker_set.stickers[-1].file_id
|
||||
assert bot.delete_sticker_from_set(file_id)
|
||||
|
||||
@flaky(10, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_bot_methods_4_tgs(self, bot, animated_sticker_set):
|
||||
sleep(1)
|
||||
file_id = animated_sticker_set.stickers[-1].file_id
|
||||
assert bot.delete_sticker_from_set(file_id)
|
||||
|
||||
def test_get_file_instance_method(self, monkeypatch, sticker):
|
||||
def test(*args, **kwargs):
|
||||
return args[1] == sticker.file_id
|
||||
|
|
Loading…
Reference in a new issue