Bot api 3.2 (#732)

NOTE: Currently not testing StickerSet in terms of bot methods (interaction with telegrams servers) as there's no delete operations.
This commit is contained in:
Jacob Bom 2017-07-22 13:34:51 +02:00 committed by Noam Meltzer
parent 881da40978
commit 5a37af6f89
6 changed files with 455 additions and 25 deletions

View file

@ -1,7 +1,6 @@
telegram.sticker module telegram.sticker module
======================= =======================
.. automodule:: telegram.sticker .. automodule:: telegram.files.sticker
:members: :members:
:undoc-members:
:show-inheritance: :show-inheritance:

View file

@ -27,7 +27,7 @@ from .files.photosize import PhotoSize
from .files.audio import Audio from .files.audio import Audio
from .files.voice import Voice from .files.voice import Voice
from .files.document import Document from .files.document import Document
from .files.sticker import Sticker from .files.sticker import Sticker, StickerSet, MaskPosition
from .files.video import Video from .files.video import Video
from .files.contact import Contact from .files.contact import Contact
from .files.location import Location from .files.location import Location
@ -120,5 +120,6 @@ __all__ = [
'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT', 'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT',
'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP', 'WebhookInfo', 'Animation', 'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP', 'WebhookInfo', 'Animation',
'Game', 'GameHighScore', 'VideoNote', 'LabeledPrice', 'SuccessfulPayment', 'ShippingOption', 'Game', 'GameHighScore', 'VideoNote', 'LabeledPrice', 'SuccessfulPayment', 'ShippingOption',
'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery', 'ChatPhoto' 'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery', 'ChatPhoto',
'StickerSet', 'MaskPosition'
] ]

View file

@ -26,7 +26,7 @@ import warnings
from datetime import datetime from datetime import datetime
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File, from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore) ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet)
from telegram.error import InvalidToken, TelegramError from telegram.error import InvalidToken, TelegramError
from telegram.utils.helpers import to_timestamp from telegram.utils.helpers import to_timestamp
from telegram.utils.request import Request from telegram.utils.request import Request
@ -2369,6 +2369,218 @@ class Bot(TelegramObject):
return result return result
def get_sticker_set(self, name, timeout=None, **kwargs):
"""
Use this method to get a sticker set.
Args:
name (:obj:`str`): Short name of the sticker set that is used in t.me/addstickers/
URLs (e.g., animals)
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.StickerSet`
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/getStickerSet'.format(self.base_url)
data = {'name': name}
result = self._request.post(url, data, timeout=timeout)
return StickerSet.de_json(result, self)
def upload_sticker_file(self, user_id, png_sticker, timeout=None, **kwargs):
"""
Use this method to upload a .png file with a sticker for later use in
:attr:`create_new_sticker_set` and :attr:`add_sticker_to_set` methods (can be used multiple
times).
Note:
The png_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 sticker file owner.
png_sticker (:obj:`str` | `filelike object`): 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.
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.File`: The uploaded File
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/uploadStickerFile'.format(self.base_url)
data = {'user_id': user_id, 'png_sticker': png_sticker}
result = self._request.post(url, data, timeout=timeout)
return File.de_json(result, self)
def create_new_sticker_set(self, user_id, name, title, png_sticker, emojis, is_masks=None,
mask_position=None, timeout=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.
Note:
The png_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`): Short name of sticker set, to be used in t.me/addstickers/ URLs
(e.g., animals). Can contain only english letters, digits and underscores.
Must begin with a letter, can't contain consecutive underscores and
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,
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.
emojis (:obj:`str`): One or more emoji corresponding to the sticker.
is_masks (:obj:`bool`, optional): Pass True, if a set of mask stickers should be
created.
mask_position (:class:`telegram.MaskPosition`, optional): Position where the mask
should be placed on faces.
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 = '{0}/createNewStickerSet'.format(self.base_url)
data = {'user_id': user_id, 'name': name, 'title': title, 'png_sticker': png_sticker,
'emojis': emojis}
if is_masks is not None:
data['is_masks'] = is_masks
if mask_position is not None:
data['mask_position'] = mask_position
result = self._request.post(url, data, timeout=timeout)
return result
def add_sticker_to_set(self, user_id, name, png_sticker, emojis, mask_position=None,
timeout=None, **kwargs):
"""
Use this method to add a new sticker to a set created by the bot.
Note:
The png_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,
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.
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.
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 = '{0}/addStickerToSet'.format(self.base_url)
data = {'user_id': user_id, 'name': name, 'png_sticker': png_sticker, 'emojis': emojis}
if mask_position is not None:
data['mask_position'] = mask_position
result = self._request.post(url, data, timeout=timeout)
return result
def set_sticker_position_in_set(self, sticker, position, timeout=None, **kwargs):
"""
Use this method to move a sticker in a set created by the bot to a specific position.
Args:
sticker (:obj:`str`): File identifier of the sticker.
position (:obj:`int`): New sticker position in the set, zero-based.
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 = '{0}/setStickerPositionInSet'.format(self.base_url)
data = {'sticker': sticker, 'position': position}
result = self._request.post(url, data, timeout=timeout)
return result
def delete_sticker_from_set(self, sticker, timeout=None, **kwargs):
"""
Use this method to delete a sticker from a set created by the bot.
Args:
sticker (:obj:`str`): File identifier of the sticker.
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 = '{0}/deleteStickerFromSet'.format(self.base_url)
data = {'sticker': sticker}
result = self._request.post(url, data, timeout=timeout)
return result
@staticmethod @staticmethod
def de_json(data, bot): def de_json(data, bot):
data = super(Bot, Bot).de_json(data, bot) data = super(Bot, Bot).de_json(data, bot)
@ -2436,3 +2648,9 @@ class Bot(TelegramObject):
setChatDescription = set_chat_description setChatDescription = set_chat_description
pinChatMessage = pin_chat_message pinChatMessage = pin_chat_message
unpinChatMessage = unpin_chat_message unpinChatMessage = unpin_chat_message
getStickerSet = get_sticker_set
uploadStickerFile = upload_sticker_file
createNewStickerSet = create_new_sticker_set
addStickerToSet = add_sticker_to_set
setStickerPositionInSet = set_sticker_position_in_set
deleteStickerFromSet = delete_sticker_from_set

View file

@ -36,7 +36,7 @@ from telegram import TelegramError
DEFAULT_MIME_TYPE = 'application/octet-stream' DEFAULT_MIME_TYPE = 'application/octet-stream'
USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)' USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)'
FILE_TYPES = ('audio', 'document', 'photo', 'sticker', 'video', 'voice', 'certificate', FILE_TYPES = ('audio', 'document', 'photo', 'sticker', 'video', 'voice', 'certificate',
'video_note') 'video_note', 'png_sticker')
class InputFile(object): class InputFile(object):

View file

@ -16,7 +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/].
"""This module contains an object that represents a Telegram Sticker.""" """This module contains objects that represents stickers."""
from telegram import PhotoSize, TelegramObject from telegram import PhotoSize, TelegramObject
@ -25,26 +25,42 @@ class Sticker(TelegramObject):
"""This object represents a Telegram Sticker. """This object represents a Telegram Sticker.
Attributes: Attributes:
file_id (str): file_id (:obj:`str`): Unique identifier for this file.
width (int): width (:obj:`int`): Sticker width.
height (int): height (:obj:`int`): Sticker height.
thumb (:class:`telegram.PhotoSize`): thumb (:class:`telegram.PhotoSize`): Optional. Sticker thumbnail in the .webp or .jpg
emoji (str): format.
file_size (int): emoji (:obj:`str`): Optional. Emoji associated with the sticker.
set_name (:obj:`str`): Optional. Name of the sticker set to which the sticker belongs.
mask_position (:class:`telegram.MaskPosition`): Optional. For mask stickers, the position
where the mask should be placed.
file_size (:obj:`int`): Optional. File size.
Args: Args:
file_id (str): file_id (:obj:`str`): Unique identifier for this file.
width (int): width (:obj:`int`): Sticker width.
height (int): height (:obj:`int`): Sticker height.
**kwargs: Arbitrary keyword arguments. thumb (:class:`telegram.PhotoSize`, optional): Sticker thumbnail in the .webp or .jpg
format.
Keyword Args: emoji (:obj:`str`, optional): Emoji associated with the sticker
thumb (Optional[:class:`telegram.PhotoSize`]): set_name (:obj:`str`, optional): Name of the sticker set to which the sticker
emoji (Optional[str]): belongs.
file_size (Optional[int]): mask_position (:class:`telegram.MaskPosition`, optional): For mask stickers, the
position where the mask should be placed.
file_size (:obj:`int`, optional): File size.
**kwargs (obj:`dict`): Arbitrary keyword arguments.
""" """
def __init__(self, file_id, width, height, thumb=None, emoji=None, file_size=None, **kwargs): def __init__(self,
file_id,
width,
height,
thumb=None,
emoji=None,
file_size=None,
set_name=None,
mask_position=None,
**kwargs):
# Required # Required
self.file_id = str(file_id) self.file_id = str(file_id)
self.width = int(width) self.width = int(width)
@ -53,6 +69,8 @@ class Sticker(TelegramObject):
self.thumb = thumb self.thumb = thumb
self.emoji = emoji self.emoji = emoji
self.file_size = file_size self.file_size = file_size
self.set_name = set_name
self.mask_position = mask_position
self._id_attrs = (self.file_id,) self._id_attrs = (self.file_id,)
@ -60,11 +78,11 @@ class Sticker(TelegramObject):
def de_json(data, bot): def de_json(data, bot):
""" """
Args: Args:
data (dict): data (:obj:`dict`):
bot (telegram.Bot): bot (telegram.Bot):
Returns: Returns:
telegram.Sticker: :obj:`telegram.Sticker`
""" """
if not data: if not data:
return None return None
@ -72,5 +90,109 @@ class Sticker(TelegramObject):
data = super(Sticker, Sticker).de_json(data, bot) data = super(Sticker, Sticker).de_json(data, bot)
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
data['mask_position'] = MaskPosition.de_json(data.get('mask_position'), bot)
return Sticker(**data) return Sticker(**data)
@staticmethod
def de_list(data, bot):
if not data:
return list()
return [Sticker.de_json(d, bot) for d in data]
class StickerSet(TelegramObject):
"""
This object represents a sticker set.
Attributes:
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
is_masks (:obj:`bool`): True, if the sticker set contains masks.
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
Args:
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
is_masks (:obj:`bool`): True, if the sticker set contains masks.
stickers (List[:class:`telegram.Sticker`]): List of all set stickers.
"""
def __init__(self, name, title, contains_masks, stickers, bot=None, **kwargs):
# TODO: telegrams docs claim contains_masks is called is_masks
# remove these lines or change once we get answer from support
self.name = name
self.title = title
self.contains_masks = contains_masks
self.stickers = stickers
self._id_attrs = (self.name,)
@staticmethod
def de_json(data, bot):
if not data:
return None
data = super(StickerSet, StickerSet).de_json(data, bot)
data['stickers'] = Sticker.de_list(data.get('stickers'), bot)
return StickerSet(bot=bot, **data)
def to_dict(self):
data = super(StickerSet, self).to_dict()
data['stickers'] = [s.to_dict() for s in data.get('stickers')]
return data
class MaskPosition(TelegramObject):
"""
This object describes the position on faces where a mask should be placed by default.
Attributes:
point (:obj:`str`): The part of the face relative to which the mask should be placed.
x_shift (:obj:`float`): Shift by X-axis measured in widths of the mask scaled to the face
size, from left to right.
y_shift (:obj:`float`): Shift by Y-axis measured in heights of the mask scaled to the face
size, from top to bottom.
zoom (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
Notes:
:attr:`type` should be one of the following: `forehead`, `eyes`, `mouth` or `chin`. You can
use the classconstants for those.
Args:
point (:obj:`str`): The part of the face relative to which the mask should be placed.
x_shift (:obj:`float`): Shift by X-axis measured in widths of the mask scaled to the face
size, from left to right. For example, choosing -1.0 will place mask just to the left
of the default mask position.
y_shift (:obj:`float`): Shift by Y-axis measured in heights of the mask scaled to the face
size, from top to bottom. For example, 1.0 will place the mask just below the default
mask position.
zoom (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
"""
FOREHEAD = 'forehead'
""":obj:`str`: 'forehead'"""
EYES = 'eyes'
""":obj:`str`: 'eyes'"""
MOUTH = 'mouth'
""":obj:`str`: 'mouth'"""
CHIN = 'chin'
""":obj:`str`: 'chin'"""
def __init__(self, point, x_shift, y_shift, zoom, **kwargs):
self.point = point
self.x_shift = x_shift
self.y_shift = y_shift
self.zoom = zoom
@staticmethod
def de_json(data, bot):
if data is None:
return None
return MaskPosition(**data)

View file

@ -226,5 +226,95 @@ class StickerTest(BaseTest, unittest.TestCase):
self.assertNotEqual(hash(a), hash(e)) self.assertNotEqual(hash(a), hash(e))
class TestStickerSet(BaseTest, unittest.TestCase):
# TODO: Implement bot tests for StickerSet
# It's hard to test creation when we can't delete sticker sets
def setUp(self):
self.name = 'test_by_{0}'.format(self._bot.username)
self.title = 'Test stickers'
self.contains_masks = False
self.stickers = [telegram.Sticker('file_id', 512, 512)]
self.json_dict = {
'name': self.name,
'title': self.title,
'contains_masks': self.contains_masks,
'stickers': [x.to_dict() for x in self.stickers]
}
def test_sticker_set_de_json(self):
sticker_set = telegram.StickerSet.de_json(self.json_dict, self._bot)
self.assertEqual(sticker_set.name, self.name)
self.assertEqual(sticker_set.title, self.title)
self.assertEqual(sticker_set.contains_masks, self.contains_masks)
self.assertEqual(sticker_set.stickers, self.stickers)
def test_sticker_set_to_json(self):
sticker_set = telegram.StickerSet.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_json(sticker_set.to_json()))
def test_sticker_set_to_dict(self):
sticker_set = telegram.StickerSet.de_json(self.json_dict, self._bot).to_dict()
self.assertTrue(self.is_dict(sticker_set))
self.assertDictEqual(self.json_dict, sticker_set)
def test_equality(self):
a = telegram.StickerSet(self.name, self.title, self.contains_masks, self.stickers)
b = telegram.StickerSet(self.name, self.title, self.contains_masks, self.stickers)
c = telegram.StickerSet(self.name, None, None, None)
d = telegram.StickerSet('blah', self.title, self.contains_masks, self.stickers)
e = telegram.Audio(self.name, 0, None, None)
self.assertEqual(a, b)
self.assertEqual(hash(a), hash(b))
self.assertIsNot(a, b)
self.assertEqual(a, c)
self.assertEqual(hash(a), hash(c))
self.assertNotEqual(a, d)
self.assertNotEqual(hash(a), hash(d))
self.assertNotEqual(a, e)
self.assertNotEqual(hash(a), hash(e))
class TestMaskPosition(BaseTest, unittest.TestCase):
def setUp(self):
self.point = telegram.MaskPosition.EYES
self.x_shift = -1
self.y_shift = 1
self.zoom = 2
self.json_dict = {
'point': self.point,
'x_shift': self.x_shift,
'y_shift': self.y_shift,
'zoom': self.zoom
}
def test_mask_position_de_json(self):
mask_position = telegram.MaskPosition.de_json(self.json_dict, self._bot)
self.assertEqual(mask_position.point, self.point)
self.assertEqual(mask_position.x_shift, self.x_shift)
self.assertEqual(mask_position.y_shift, self.y_shift)
self.assertEqual(mask_position.zoom, self.zoom)
def test_mask_positiont_to_json(self):
mask_position = telegram.MaskPosition.de_json(self.json_dict, self._bot)
self.assertTrue(self.is_json(mask_position.to_json()))
def test_mask_position_to_dict(self):
mask_position = telegram.MaskPosition.de_json(self.json_dict, self._bot).to_dict()
self.assertTrue(self.is_dict(mask_position))
self.assertDictEqual(self.json_dict, mask_position)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()