Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api 4.8 #1917

Merged
merged 7 commits into from
May 2, 2020
Merged

Api 4.8 #1917

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.7** are supported.
All types and methods of the Telegram Bot API **4.8** are supported.

==========
Installing
Expand Down
38 changes: 38 additions & 0 deletions telegram/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3633,6 +3633,10 @@ def send_poll(self,
reply_to_message_id=None,
reply_markup=None,
timeout=None,
explanation=None,
explanation_parse_mode=DEFAULT_NONE,
open_period=None,
close_date=None,
**kwargs):
"""
Use this method to send a native poll.
Expand All @@ -3650,6 +3654,18 @@ def send_poll(self,
answers, ignored for polls in quiz mode, defaults to False.
correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer
option, required for polls in quiz mode.
explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect
answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most
2 line feeds after entities parsing.
explanation_parse_mode (:obj:`str`, optional): Mode for parsing entities in the
explanation. See the constants in :class:`telegram.ParseMode` for the available
modes.
open_period (:obj:`int`, optional): Amount of time in seconds the poll will be active
after creation, 5-600. Can't be used together with :attr:`close_date`.
close_date (:obj:`int` | :obj:`datetime.datetime`, optional): Point in time (Unix
timestamp) when the poll will be automatically closed. Must be at least 5 and no
more than 600 seconds in the future. Can't be used together with
:attr:`open_period`.
is_closed (:obj:`bool`, optional): Pass True, if the poll needs to be immediately
closed. This can be useful for poll preview.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
Expand Down Expand Up @@ -3679,6 +3695,12 @@ def send_poll(self,
'options': options
}

if explanation_parse_mode == DEFAULT_NONE:
if self.defaults:
explanation_parse_mode = self.defaults.parse_mode
else:
explanation_parse_mode = None

if not is_anonymous:
data['is_anonymous'] = is_anonymous
if type:
Expand All @@ -3689,6 +3711,16 @@ def send_poll(self,
data['correct_option_id'] = correct_option_id
if is_closed:
data['is_closed'] = is_closed
if explanation:
data['explanation'] = explanation
if explanation_parse_mode:
data['explanation_parse_mode'] = explanation_parse_mode
if open_period:
data['open_period'] = open_period
if close_date:
if isinstance(close_date, datetime):
close_date = to_timestamp(close_date)
data['close_date'] = close_date

return self._message(url, data, timeout=timeout, disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id, reply_markup=reply_markup,
Expand Down Expand Up @@ -3749,13 +3781,16 @@ def send_dice(self,
reply_to_message_id=None,
reply_markup=None,
timeout=None,
emoji=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.
emoji (:obj:`str`, optional): Emoji on which the dice throw animation is based.
Currently, must be one of “🎲” or “🎯”. Defaults to “🎲”
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
Expand All @@ -3781,6 +3816,9 @@ def send_dice(self,
'chat_id': chat_id,
}

if emoji:
data['emoji'] = emoji

return self._message(url, data, timeout=timeout, disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id, reply_markup=reply_markup,
**kwargs)
Expand Down
23 changes: 20 additions & 3 deletions telegram/dice.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,38 @@

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".)
This object represents a dice with random value from 1 to 6 for currently supported base eomji.
(The singular form of "dice" is "die". However, PTB mimics the Telegram API, which uses the
term "dice".)

Note:
If :attr:`emoji` is "🎯", a value of 6 currently represents a bullseye, while a value of 1
indicates that the dartboard was missed. However, this behaviour is undocumented and might
be changed by Telegram.

Attributes:
value (:obj:`int`): Value of the dice.
emoji (:obj:`str`): Emoji on which the dice throw animation is based.

Args:
value (:obj:`int`): Value of the dice, 1-6.
emoji (:obj:`str`): Emoji on which the dice throw animation is based.
"""
def __init__(self, value, **kwargs):
def __init__(self, value, emoji, **kwargs):
self.value = value
self.emoji = emoji

@classmethod
def de_json(cls, data, bot):
if not data:
return None

return cls(**data)

DICE = '🎲'
""":obj:`str`: '🎲'"""
DARTS = '🎯'
""":obj:`str`: '🎯'"""
ALL_EMOJI = [DICE, DARTS]
"""List[:obj:`str`]: List of all supported base emoji. Currently :attr:`DICE` and
:attr:`DARTS`."""
61 changes: 41 additions & 20 deletions telegram/ext/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,38 @@ def __repr__(self):
self.and_filter or self.or_filter)


class _DiceEmoji(BaseFilter):

def __init__(self, emoji=None, name=None):
self.name = 'Filters.dice.{}'.format(name) if name else 'Filters.dice'
self.emoji = emoji

class _DiceValues(BaseFilter):

def __init__(self, values, name, emoji=None):
self.values = [values] if isinstance(values, int) else values
self.emoji = emoji
self.name = '{}({})'.format(name, values)

def filter(self, message):
if bool(message.dice and message.dice.value in self.values):
if self.emoji:
return message.dice.emoji == self.emoji
return True

def __call__(self, update):
if isinstance(update, Update):
return self.filter(update.effective_message)
else:
return self._DiceValues(update, self.name, emoji=self.emoji)

def filter(self, message):
if bool(message.dice):
if self.emoji:
return message.dice.emoji == self.emoji
return True


class Filters(object):
"""Predefined filters for use as the `filter` argument of :class:`telegram.ext.MessageHandler`.

Expand Down Expand Up @@ -967,26 +999,9 @@ def filter(self, message):
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)
class _Dice(_DiceEmoji):
dice = _DiceEmoji('🎲', 'dice')
darts = _DiceEmoji('🎯', 'darts')

dice = _Dice()
"""Dice Messages. If an integer or a list of integers is passed, it filters messages to only
Expand All @@ -1007,6 +1022,12 @@ def filter(self, message):
Note:
Dice messages don't have text. If you want to filter either text or dice messages, use
``Filters.text | Filters.dice``.

Attributes:
dice: Dice messages with the emoji 🎲. Passing a list of integers is supported just as for
:attr:`Filters.dice`.
darts: Dice messages with the emoji 🎯. Passing a list of integers is supported just as for
:attr:`Filters.dice`.
"""

class language(BaseFilter):
Expand Down
101 changes: 98 additions & 3 deletions telegram/poll.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Poll."""

from telegram import (TelegramObject, User)
import sys

from telegram import (TelegramObject, User, MessageEntity)
from telegram.utils.helpers import to_timestamp, from_timestamp


class PollOption(TelegramObject):
Expand Down Expand Up @@ -95,6 +98,14 @@ class Poll(TelegramObject):
type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`.
allows_multiple_answers (:obj:`bool`): True, if the poll allows multiple answers.
correct_option_id (:obj:`int`): Optional. Identifier of the correct answer option.
explanation (:obj:`str`): Optional. Text that is shown when a user chooses an incorrect
answer or taps on the lamp icon in a quiz-style poll.
explanation_entities (List[:class:`telegram.MessageEntity`]): Optional. Special entities
like usernames, URLs, bot commands, etc. that appear in the :attr:`explanation`.
open_period (:obj:`int`): Optional. Amount of time in seconds the poll will be active
after creation.
close_date (:obj:`datetime.datetime`): Optional. Point in time when the poll will be
automatically closed.

Args:
id (:obj:`str`): Unique poll identifier.
Expand All @@ -107,11 +118,32 @@ class Poll(TelegramObject):
correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer option.
Available only for polls in the quiz mode, which are closed, or was sent (not
forwarded) by the bot or to the private chat with the bot.
explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect
answer or taps on the lamp icon in a quiz-style poll, 0-200 characters.
explanation_entities (List[:class:`telegram.MessageEntity`], optional): Special entities
like usernames, URLs, bot commands, etc. that appear in the :attr:`explanation`.
open_period (:obj:`int`, optional): Amount of time in seconds the poll will be active
after creation.
close_date (:obj:`datetime.datetime`, optional): Point in time (Unix timestamp) when the
poll will be automatically closed. Converted to :obj:`datetime.datetime`.

"""

def __init__(self, id, question, options, total_voter_count, is_closed, is_anonymous, type,
allows_multiple_answers, correct_option_id=None, **kwargs):
def __init__(self,
id,
question,
options,
total_voter_count,
is_closed,
is_anonymous,
type,
allows_multiple_answers,
correct_option_id=None,
explanation=None,
explanation_entities=None,
open_period=None,
close_date=None,
**kwargs):
self.id = id
self.question = question
self.options = options
Expand All @@ -121,6 +153,10 @@ def __init__(self, id, question, options, total_voter_count, is_closed, is_anony
self.type = type
self.allows_multiple_answers = allows_multiple_answers
self.correct_option_id = correct_option_id
self.explanation = explanation
self.explanation_entities = explanation_entities
self.open_period = open_period
self.close_date = close_date

self._id_attrs = (self.id,)

Expand All @@ -132,16 +168,75 @@ def de_json(cls, data, bot):
data = super(Poll, cls).de_json(data, bot)

data['options'] = [PollOption.de_json(option, bot) for option in data['options']]
data['explanation_entities'] = MessageEntity.de_list(data.get('explanation_entities'), bot)
data['close_date'] = from_timestamp(data.get('close_date'))

return cls(**data)

def to_dict(self):
data = super(Poll, self).to_dict()

data['options'] = [x.to_dict() for x in self.options]
if self.explanation_entities:
data['explanation_entities'] = [e.to_dict() for e in self.explanation_entities]
data['close_date'] = to_timestamp(data.get('close_date'))

return data

def parse_explanation_entity(self, entity):
"""Returns the text from a given :class:`telegram.MessageEntity`.

Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``Message.text`` with the offset and length.)

Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to this message.

Returns:
:obj:`str`: The text of the given entity.

"""
# Is it a narrow build, if so we don't need to convert
if sys.maxunicode == 0xffff:
return self.explanation[entity.offset:entity.offset + entity.length]
else:
entity_text = self.explanation.encode('utf-16-le')
entity_text = entity_text[entity.offset * 2:(entity.offset + entity.length) * 2]

return entity_text.decode('utf-16-le')

def parse_explanation_entities(self, types=None):
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this polls explanation filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.

Note:
This method should always be used instead of the :attr:`explanation_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_explanation_entity` for more info.

Args:
types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.

Returns:
Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.

"""
if types is None:
types = MessageEntity.ALL_TYPES

return {
entity: self.parse_explanation_entity(entity)
for entity in self.explanation_entities if entity.type in types
}

REGULAR = "regular"
""":obj:`str`: 'regular'"""
QUIZ = "quiz"
Expand Down