Repository: python-telegram-bot/python-telegram-bot
Pull request: 4.6 API update
docs need to be built and checked. Tests should run through now.

- [x] When #1508 is merged to master, merge master and update `Message._parse_*` to handle the `MessageEntities.language` attribute correctly. Doesn't actually do anything yet, but we should still add it to `Message.text_html` and friends
- [x] Make `test_official` fail again (i.e. partly revert #1716)

closes #1722

Problem statement:
API 4.6
- [x] Polls 2.0
  - [x] New parameters for `sendPoll`
  - [x] New object `KeyboardButtonPollType` and corresponding field for `KeyboardButton`
  - [x] Hew object `PollAnswer` and corresponding field for `Update`
  - [x] New fields for `Poll`
- [x] New fields for the `User` object
- [x] New field `language` for `MessageEntity`

If you'd like to start on anything, please comment below, what you'll be doing, so that we don't do the work twice :)

Hints from issue discussion:
Id like to do something.

Edit: Gonna check what I have done so far.

Commit message: added poll_answer + poll filter tests

Patch to review:
diff --git a/docs/source/telegram.keyboardbuttonpolltype.rst b/docs/source/telegram.keyboardbuttonpolltype.rst
new file mode 100644
index 00000000000..fa4315abd71
--- /dev/null
+++ b/docs/source/telegram.keyboardbuttonpolltype.rst
@@ -0,0 +1,6 @@
+telegram.KeyboardButtonPollType
+===============================
+
+.. autoclass:: telegram.KeyboardButtonPollType
+    :members:
+    :show-inheritance:
diff --git a/docs/source/telegram.pollanswer.rst b/docs/source/telegram.pollanswer.rst
new file mode 100644
index 00000000000..b74899ebfbc
--- /dev/null
+++ b/docs/source/telegram.pollanswer.rst
@@ -0,0 +1,6 @@
+telegram.PollAnswer
+===================
+
+.. autoclass:: telegram.PollAnswer
+    :members:
+    :show-inheritance:
diff --git a/docs/source/telegram.rst b/docs/source/telegram.rst
index 85d51cabe1d..718308b9e79 100644
--- a/docs/source/telegram.rst
+++ b/docs/source/telegram.rst
@@ -31,6 +31,7 @@ telegram package
     telegram.inputmediaphoto
     telegram.inputmediavideo
     telegram.keyboardbutton
+    telegram.keyboardbuttonpolltype
     telegram.location
     telegram.loginurl
     telegram.message
@@ -38,6 +39,7 @@ telegram package
     telegram.parsemode
     telegram.photosize
     telegram.poll
+    telegram.pollanswer
     telegram.polloption
     telegram.replykeyboardremove
     telegram.replykeyboardmarkup
diff --git a/telegram/__init__.py b/telegram/__init__.py
index 76ad1281a76..d1cfce269d1 100644
--- a/telegram/__init__.py
+++ b/telegram/__init__.py
@@ -38,6 +38,7 @@
 from .chataction import ChatAction
 from .userprofilephotos import UserProfilePhotos
 from .keyboardbutton import KeyboardButton
+from .keyboardbuttonpolltype import KeyboardButtonPollType
 from .replymarkup import ReplyMarkup
 from .replykeyboardmarkup import ReplyKeyboardMarkup
 from .replykeyboardremove import ReplyKeyboardRemove
@@ -48,7 +49,7 @@
 from .parsemode import ParseMode
 from .messageentity import MessageEntity
 from .games.game import Game
-from .poll import Poll, PollOption
+from .poll import Poll, PollOption, PollAnswer
 from .loginurl import LoginUrl
 from .games.callbackgame import CallbackGame
 from .payment.shippingaddress import ShippingAddress
@@ -138,7 +139,7 @@
     'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo',
     'InlineQueryResultVoice', 'InlineQueryResultGame', 'InputContactMessageContent', 'InputFile',
     'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent',
-    'InputVenueMessageContent', 'KeyboardButton', 'Location', 'EncryptedCredentials',
+    'InputVenueMessageContent', 'Location', 'EncryptedCredentials',
     'PassportFile', 'EncryptedPassportElement', 'PassportData', 'Message', 'MessageEntity',
     'ParseMode', 'PhotoSize', 'ReplyKeyboardRemove', 'ReplyKeyboardMarkup', 'ReplyMarkup',
     'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
@@ -156,5 +157,5 @@
     'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
     'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
     'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
-    'PollOption', 'LoginUrl'
+    'PollOption', 'PollAnswer', 'LoginUrl', 'KeyboardButton', 'KeyboardButtonPollType',
 ]
diff --git a/telegram/bot.py b/telegram/bot.py
index bb9b3ecf5b2..4d7d15e8fa8 100644
--- a/telegram/bot.py
+++ b/telegram/bot.py
@@ -171,6 +171,27 @@ def username(self):
 
         return self.bot.username
 
+    @property
+    @info
+    def can_join_groups(self):
+        """:obj:`str`: Bot's can_join_groups attribute."""
+
+        return self.bot.can_join_groups
+
+    @property
+    @info
+    def can_read_all_group_messages(self):
+        """:obj:`str`: Bot's can_read_all_group_messages attribute."""
+
+        return self.bot.can_read_all_group_messages
+
+    @property
+    @info
+    def supports_inline_queries(self):
+        """:obj:`str`: Bot's supports_inline_queries attribute."""
+
+        return self.bot.supports_inline_queries
+
     @property
     def name(self):
         """:obj:`str`: Bot's @username."""
@@ -3348,18 +3369,33 @@ def send_poll(self,
                   chat_id,
                   question,
                   options,
+                  is_anonymous=True,
+                  type=Poll.REGULAR,
+                  allows_multiple_answers=False,
+                  correct_option_id=None,
+                  is_closed=None,
                   disable_notification=None,
                   reply_to_message_id=None,
                   reply_markup=None,
                   timeout=None,
                   **kwargs):
         """
-        Use this method to send a native poll. A native poll can't be sent to a private chat.
+        Use this method to send a native poll.
 
         Args:
             chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target private chat.
             question (:obj:`str`): Poll question, 1-255 characters.
             options (List[:obj:`str`]): List of answer options, 2-10 strings 1-100 characters each.
+            is_anonymous (:obj:`bool`, optional): True, if the poll needs to be anonymous,
+                defaults to True.
+            type (:obj:`str`, optional): Poll type, :attr:`telegram.Poll.QUIZ` or
+                :attr:`telegram.Poll.REGULAR`, defaults to :attr:`telegram.Poll.REGULAR`.
+            allows_multiple_answers (:obj:`bool`, optional): True, if the poll allows multiple
+                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
+            is_closed (:obj:`bool`, optional): Pass True, if the poll needs to be immediately
+                closed
             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
@@ -3387,6 +3423,17 @@ def send_poll(self,
             'options': options
         }
 
+        if is_anonymous:
+            data['is_anonymous'] = is_anonymous
+        if type:
+            data['type'] = type
+        if allows_multiple_answers:
+            data['allows_multiple_answers'] = allows_multiple_answers
+        if correct_option_id:
+            data['correct_option_id'] = correct_option_id
+        if is_closed:
+            data['is_closed'] = is_closed
+
         return self._message(url, data, timeout=timeout, disable_notification=disable_notification,
                              reply_to_message_id=reply_to_message_id, reply_markup=reply_markup,
                              **kwargs)
diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py
index 8e4eac8012a..8dd67db0ea4 100644
--- a/telegram/ext/filters.py
+++ b/telegram/ext/filters.py
@@ -1036,6 +1036,22 @@ def filter(self, update):
 
         channel_posts = _ChannelPosts()
 
+        class _Poll(BaseFilter):
+            update_filter = True
+
+            def filter(self, update):
+                return update.poll
+
+        poll = _Poll()
+
+        class _PollAnswer(BaseFilter):
+            update_filter = True
+
+            def filter(self, update):
+                return update.poll_answer
+
+        poll_answer = _PollAnswer()
+
         def filter(self, update):
             return self.messages(update) or self.channel_posts(update)
 
@@ -1057,4 +1073,6 @@ def filter(self, update):
             :attr:`telegram.Update.edited_channel_post`
         channel_posts: Updates with either :attr:`telegram.Update.channel_post` or
             :attr:`telegram.Update.edited_channel_post`
+        poll: Updates with :attr:`telegram.Update.poll`
+        poll_answer: Updates with :attr:`telegram.Update.poll_answer`
     """
diff --git a/telegram/keyboardbutton.py b/telegram/keyboardbutton.py
index a8ed639ac81..88be98bf008 100644
--- a/telegram/keyboardbutton.py
+++ b/telegram/keyboardbutton.py
@@ -33,6 +33,7 @@ class KeyboardButton(TelegramObject):
         text (:obj:`str`): Text of the button.
         request_contact (:obj:`bool`): Optional. If the user's phone number will be sent.
         request_location (:obj:`bool`): Optional. If the user's current location will be sent.
+        request_poll (:class:`KeyboardButtonPollType`): Optional. If the user should create a poll.
 
     Args:
         text (:obj:`str`): Text of the button. If none of the optional fields are used, it will be
@@ -41,16 +42,24 @@ class KeyboardButton(TelegramObject):
             a contact when the button is pressed. Available in private chats only.
         request_location (:obj:`bool`, optional): If True, the user's current location will be sent
             when the button is pressed. Available in private chats only.
+        request_poll (:class:`KeyboardButtonPollType`, optional): If specified, the user will be
+            asked to create a poll and send it to the bot when the button is pressed. Available in
+            private chats only.
 
     Note:
         :attr:`request_contact` and :attr:`request_location` options will only work in Telegram
         versions released after 9 April, 2016. Older clients will ignore them.
 
+        :attr:`request_poll` option will only work in Telegram versions released after 23 January,
+        2020. Older clients will receive unsupported message.
+
     """
 
-    def __init__(self, text, request_contact=None, request_location=None, **kwargs):
+    def __init__(self, text, request_contact=None, request_location=None, request_poll=None,
+                 **kwargs):
         # Required
         self.text = text
         # Optionals
         self.request_contact = request_contact
         self.request_location = request_location
+        self.request_poll = request_poll
diff --git a/telegram/keyboardbuttonpolltype.py b/telegram/keyboardbuttonpolltype.py
new file mode 100644
index 00000000000..39c2bb48708
--- /dev/null
+++ b/telegram/keyboardbuttonpolltype.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# pylint: disable=R0903
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 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 type of a Telegram Poll."""
+from telegram import TelegramObject
+
+
+class KeyboardButtonPollType(TelegramObject):
+    """This object represents type of a poll, which is allowed to be created
+    and sent when the corresponding button is pressed.
+
+    Attributes:
+        type (:obj:`str`): Optional. If :attr:`telegram.Poll.QUIZ` is passed, the user will be
+            allowed to create only polls in the quiz mode. If :attr:`telegram.Poll.REGULAR` is
+            passed, only regular polls will be allowed. Otherwise, the user will be allowed to
+            create a poll of any type.
+    """
+    def __init__(self, type=None):
+        self.type = type
+
+        self._id_attrs = (self.type,)
diff --git a/telegram/messageentity.py b/telegram/messageentity.py
index 9208d8c8f8a..5963fa42ac9 100644
--- a/telegram/messageentity.py
+++ b/telegram/messageentity.py
@@ -32,6 +32,8 @@ class MessageEntity(TelegramObject):
         length (:obj:`int`): Length of the entity in UTF-16 code units.
         url (:obj:`str`): Optional. Url that will be opened after user taps on the text.
         user (:class:`telegram.User`): Optional. The mentioned user.
+        language (:obj:`str`): Optional. Programming language of the entity
+            text
 
     Args:
         type (:obj:`str`): Type of the entity. Can be mention (@username), hashtag, bot_command,
@@ -40,13 +42,16 @@ class MessageEntity(TelegramObject):
             without usernames).
         offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity.
         length (:obj:`int`): Length of the entity in UTF-16 code units.
-        url (:obj:`str`, optional): For "text_link" only, url that will be opened after usertaps on
-            the text.
-        user (:class:`telegram.User`, optional): For "text_mention" only, the mentioned user.
+        url (:obj:`str`, optional): For :attr:`TEXT_LINK` only, url that will be opened after
+            usertaps on the text.
+        user (:class:`telegram.User`, optional): For :attr:`TEXT_MENTION` only, the mentioned
+             user.
+        language (:obj:`str`, optional): For :attr:`PRE` only, the programming language of
+            the entity text
 
     """
 
-    def __init__(self, type, offset, length, url=None, user=None, **kwargs):
+    def __init__(self, type, offset, length, url=None, user=None, language=None, **kwargs):
         # Required
         self.type = type
         self.offset = offset
@@ -54,6 +59,7 @@ def __init__(self, type, offset, length, url=None, user=None, **kwargs):
         # Optionals
         self.url = url
         self.user = user
+        self.language = language
 
         self._id_attrs = (self.type, self.offset, self.length)
 
diff --git a/telegram/poll.py b/telegram/poll.py
index 0c22d1bb2fb..af8a6af2977 100644
--- a/telegram/poll.py
+++ b/telegram/poll.py
@@ -19,7 +19,7 @@
 # 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)
+from telegram import (TelegramObject, User)
 
 
 class PollOption(TelegramObject):
@@ -48,6 +48,39 @@ def de_json(cls, data, bot):
         return cls(**data)
 
 
+class PollAnswer(TelegramObject):
+    """
+    This object represents an answer of a user in a non-anonymous poll.
+
+    Attributes:
+        poll_id (:obj:`str`): Unique poll identifier.
+        user (:class:`telegram.User`): The user, who changed the answer to the poll.
+        option_ids (List[:obj:`int`]): Identifiers of answer options, chosen by the user.
+
+    Args:
+        poll_id (:obj:`str`): Unique poll identifier.
+        user (:class:`telegram.User`): The user, who changed the answer to the poll.
+        option_ids (List[:obj:`int`]): 0-based identifiers of answer options, chosen by the user.
+            May be empty if the user retracted their vote.
+
+    """
+    def __init__(self, poll_id, user, option_ids, **kwargs):
+        self.poll_id = poll_id
+        self.user = user
+        self.option_ids = option_ids
+
+    @classmethod
+    def de_json(cls, data, bot):
+        if not data:
+            return None
+
+        data = super(PollAnswer, cls).de_json(data, bot)
+
+        data['user'] = User.de_json(data.get('user'), bot)
+
+        return cls(**data)
+
+
 class Poll(TelegramObject):
     """
     This object contains information about a poll.
@@ -56,21 +89,38 @@ class Poll(TelegramObject):
         id (:obj:`str`): Unique poll identifier.
         question (:obj:`str`): Poll question, 1-255 characters.
         options (List[:class:`PollOption`]): List of poll options.
+        total_voter_count (:obj:`int`): Total number of users that voted in the poll.
         is_closed (:obj:`bool`): True, if the poll is closed.
+        is_anonymous (:obj:`bool`): True, if the poll is anonymous.
+        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.
 
     Args:
         id (:obj:`str`): Unique poll identifier.
         question (:obj:`str`): Poll question, 1-255 characters.
         options (List[:class:`PollOption`]): List of poll options.
         is_closed (:obj:`bool`): True, if the poll is closed.
+        is_anonymous (:obj:`bool`): True, if the poll is anonymous.
+        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): 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.
 
     """
 
-    def __init__(self, id, question, options, is_closed, **kwargs):
+    def __init__(self, id, question, options, total_voter_count, is_closed, is_anonymous, type,
+                 allows_multiple_answers, correct_option_id=None, **kwargs):
         self.id = id
         self.question = question
         self.options = options
+        self.total_voter_count = total_voter_count
         self.is_closed = is_closed
+        self.is_anonymous = is_anonymous
+        self.type = type
+        self.allows_multiple_answers = allows_multiple_answers
+        self.correct_option_id = correct_option_id
 
         self._id_attrs = (self.id,)
 
@@ -91,3 +141,8 @@ def to_dict(self):
         data['options'] = [x.to_dict() for x in self.options]
 
         return data
+
+    REGULAR = "regular"
+    """:obj:`str`: 'regular'"""
+    QUIZ = "quiz"
+    """:obj:`str`: 'quiz'"""
diff --git a/telegram/update.py b/telegram/update.py
index b05d661249a..9de8db99ae5 100644
--- a/telegram/update.py
+++ b/telegram/update.py
@@ -20,6 +20,7 @@
 
 from telegram import (Message, TelegramObject, InlineQuery, ChosenInlineResult,
                       CallbackQuery, ShippingQuery, PreCheckoutQuery, Poll)
+from telegram.poll import PollAnswer
 
 
 class Update(TelegramObject):
@@ -42,7 +43,10 @@ class Update(TelegramObject):
         pre_checkout_query (:class:`telegram.PreCheckoutQuery`): Optional. New incoming
             pre-checkout query.
         poll (:class:`telegram.Poll`): Optional. New poll state. Bots receive only updates
-            about polls, which are sent or stopped by the bot
+            about stopped polls and polls, which are sent by the bot
+        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
+            by the bot itself.
 
     Args:
         update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
@@ -67,6 +71,9 @@ class Update(TelegramObject):
             pre-checkout query. Contains full information about checkout
         poll (:class:`telegram.Poll`, optional): New poll state. Bots receive only updates
             about polls, which are sent or stopped by the bot
+        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
+            by the bot itself.
         **kwargs (:obj:`dict`): Arbitrary keyword arguments.
 
     """
@@ -83,6 +90,7 @@ def __init__(self,
                  shipping_query=None,
                  pre_checkout_query=None,
                  poll=None,
+                 poll_answer=None,
                  **kwargs):
         # Required
         self.update_id = int(update_id)
@@ -97,6 +105,7 @@ def __init__(self,
         self.channel_post = channel_post
         self.edited_channel_post = edited_channel_post
         self.poll = poll
+        self.poll_answer = poll_answer
 
         self._effective_user = None
         self._effective_chat = None
@@ -137,6 +146,9 @@ def effective_user(self):
         elif self.pre_checkout_query:
             user = self.pre_checkout_query.from_user
 
+        elif self.poll_answer:
+            user = self.poll_answer.user
+
         self._effective_user = user
         return user
 
@@ -146,7 +158,8 @@ def effective_chat(self):
         :class:`telegram.Chat`: The chat that this update was sent in, no matter what kind of
             update this is. Will be ``None`` for :attr:`inline_query`,
             :attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
-            :attr:`shipping_query`, :attr:`pre_checkout_query` and :attr:`poll`.
+            :attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll` and
+            :attr:`poll_answer`.
 
         """
         if self._effective_chat:
@@ -178,7 +191,8 @@ def effective_message(self):
         :class:`telegram.Message`: The message included in this update, no matter what kind of
             update this is. Will be ``None`` for :attr:`inline_query`,
             :attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
-            :attr:`shipping_query`, :attr:`pre_checkout_query` and :attr:`poll`.
+            :attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll` and
+            :attr:`poll_answer`.
 
         """
         if self._effective_message:
@@ -222,5 +236,6 @@ def de_json(cls, data, bot):
         data['channel_post'] = Message.de_json(data.get('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_answer'] = PollAnswer.de_json(data.get('poll_answer'), bot)
 
         return cls(**data)
diff --git a/telegram/user.py b/telegram/user.py
index e140cfc0216..4c34adeef09 100644
--- a/telegram/user.py
+++ b/telegram/user.py
@@ -34,6 +34,12 @@ class User(TelegramObject):
         last_name (:obj:`str`): Optional. User's or bot's last name.
         username (:obj:`str`): Optional. User's or bot's username.
         language_code (:obj:`str`): Optional. IETF language tag of the user's language.
+        can_join_groups (:obj:`str`): Optional. True, if the bot can be invited to groups.
+            Returned only in :attr:`telegram.Bot.get_me` requests.
+        can_read_all_group_messages (:obj:`str`): Optional. True, if privacy mode is disabled
+            for the bot. Returned only in :attr:`telegram.Bot.get_me` requests.
+        supports_inline_queries (:obj:`str`): Optional. True, if the bot supports inline queries.
+            Returned only in :attr:`telegram.Bot.get_me` requests.
         bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
 
     Args:
@@ -43,6 +49,12 @@ class User(TelegramObject):
         last_name (:obj:`str`, optional): User's or bot's last name.
         username (:obj:`str`, optional): User's or bot's username.
         language_code (:obj:`str`, optional): IETF language tag of the user's language.
+        can_join_groups (:obj:`str`, optional): True, if the bot can be invited to groups.
+            Returned only in :attr:`telegram.Bot.get_me` requests.
+        can_read_all_group_messages (:obj:`str`, optional): True, if privacy mode is disabled
+            for the bot. Returned only in :attr:`telegram.Bot.get_me` requests.
+        supports_inline_queries (:obj:`str`, optional): True, if the bot supports inline queries.
+            Returned only in :attr:`telegram.Bot.get_me` requests.
         bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
 
     """
@@ -54,6 +66,9 @@ def __init__(self,
                  last_name=None,
                  username=None,
                  language_code=None,
+                 can_join_groups=None,
+                 can_read_all_group_messages=None,
+                 supports_inline_queries=None,
                  bot=None,
                  **kwargs):
         # Required
@@ -64,7 +79,9 @@ def __init__(self,
         self.last_name = last_name
         self.username = username
         self.language_code = language_code
-
+        self.can_join_groups = can_join_groups
+        self.can_read_all_group_messages = can_read_all_group_messages
+        self.supports_inline_queries = supports_inline_queries
         self.bot = bot
 
         self._id_attrs = (self.id,)
@@ -99,7 +116,6 @@ def link(self):
     def de_json(cls, data, bot):
         if not data:
             return None
-
         data = super(User, cls).de_json(data, bot)
 
         return cls(bot=bot, **data)
diff --git a/telegram/utils/helpers.py b/telegram/utils/helpers.py
index e56eb11e48d..15204bc029e 100644
--- a/telegram/utils/helpers.py
+++ b/telegram/utils/helpers.py
@@ -100,7 +100,7 @@ def to_float_timestamp(t, reference_timestamp=None):
     Converts a given time object to a float POSIX timestamp.
     Used to convert different time specifications to a common format. The time object
     can be relative (i.e. indicate a time increment, or a time of day) or absolute.
-    Any objects from the :module:`datetime` module that are timezone-naive will be assumed
+    Any objects from the :class:`datetime` module that are timezone-naive will be assumed
     to be in UTC.
 
     ``None`` s are left alone (i.e. ``to_float_timestamp(None)`` is ``None``).
diff --git a/tests/test_bot.py b/tests/test_bot.py
index 586d13ccdd1..9597f55d4f4 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -88,6 +88,9 @@ def test_get_me_and_properties(self, bot):
         assert get_me_bot.first_name == bot.first_name
         assert get_me_bot.last_name == bot.last_name
         assert get_me_bot.name == bot.name
+        assert get_me_bot.can_join_groups == bot.can_join_groups
+        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
 
     @flaky(3, 1)
     @pytest.mark.timeout(10)
@@ -175,14 +178,17 @@ def test_send_and_stop_poll(self, bot, super_group_id):
         question = 'Is this a test?'
         answers = ['Yes', 'No', 'Maybe']
         message = bot.send_poll(chat_id=super_group_id, question=question, options=answers,
-                                timeout=60)
+                                is_anonymous=False, allows_multiple_answers=True, timeout=60)
 
         assert message.poll
         assert message.poll.question == question
         assert message.poll.options[0].text == answers[0]
         assert message.poll.options[1].text == answers[1]
         assert message.poll.options[2].text == answers[2]
+        assert not message.poll.is_anonymous
+        assert message.poll.allows_multiple_answers
         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)
         assert isinstance(poll, Poll)
@@ -194,6 +200,13 @@ def test_send_and_stop_poll(self, bot, super_group_id):
         assert poll.options[2].text == answers[2]
         assert poll.options[2].voter_count == 0
         assert poll.question == question
+        assert poll.total_voter_count == 0
+
+        message_quiz = bot.send_poll(chat_id=super_group_id, question=question, options=answers,
+                                     type=Poll.QUIZ, correct_option_id=2, is_closed=True)
+        assert message_quiz.poll.correct_option_id == 2
+        assert message_quiz.poll.type == Poll.QUIZ
+        assert message_quiz.poll.is_closed
 
     @flaky(3, 1)
     @pytest.mark.timeout(10)
diff --git a/tests/test_filters.py b/tests/test_filters.py
index b1525b87e4a..b9a7073a8c8 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -739,6 +739,16 @@ def test_update_type_edited_channel_post(self, update):
         assert Filters.update.channel_posts(update)
         assert Filters.update(update)
 
+    def test_update_type_poll(self, update):
+        update.poll = True
+        assert Filters.update.poll(update)
+        assert Filters.update(update)
+
+    def test_update_type_poll_answer(self, update):
+        update.poll_answer = True
+        assert Filters.update.poll_answer(update)
+        assert Filters.update(update)
+
     def test_merged_short_circuit_and(self, update):
         update.message.text = '/test'
 
diff --git a/tests/test_keyboardbutton.py b/tests/test_keyboardbutton.py
index 69eeaf80287..5a2d957c7d5 100644
--- a/tests/test_keyboardbutton.py
+++ b/tests/test_keyboardbutton.py
@@ -20,24 +20,28 @@
 import pytest
 
 from telegram import KeyboardButton
+from telegram.keyboardbuttonpolltype import KeyboardButtonPollType
 
 
 @pytest.fixture(scope='class')
 def keyboard_button():
     return KeyboardButton(TestKeyboardButton.text,
                           request_location=TestKeyboardButton.request_location,
-                          request_contact=TestKeyboardButton.request_contact)
+                          request_contact=TestKeyboardButton.request_contact,
+                          request_poll=TestKeyboardButton.request_poll)
 
 
 class TestKeyboardButton(object):
     text = 'text'
     request_location = True
     request_contact = True
+    request_poll = KeyboardButtonPollType("quiz")
 
     def test_expected_values(self, keyboard_button):
         assert keyboard_button.text == self.text
         assert keyboard_button.request_location == self.request_location
         assert keyboard_button.request_contact == self.request_contact
+        assert keyboard_button.request_poll == self.request_poll
 
     def test_to_dict(self, keyboard_button):
         keyboard_button_dict = keyboard_button.to_dict()
@@ -46,3 +50,4 @@ def test_to_dict(self, keyboard_button):
         assert keyboard_button_dict['text'] == keyboard_button.text
         assert keyboard_button_dict['request_location'] == keyboard_button.request_location
         assert keyboard_button_dict['request_contact'] == keyboard_button.request_contact
+        assert keyboard_button_dict['request_poll'] == keyboard_button.request_poll.to_dict()
diff --git a/tests/test_keyboardbuttonpolltype.py b/tests/test_keyboardbuttonpolltype.py
new file mode 100644
index 00000000000..93394ae6147
--- /dev/null
+++ b/tests/test_keyboardbuttonpolltype.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 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 KeyboardButtonPollType, Poll
+
+
+@pytest.fixture(scope='class')
+def keyboard_button_poll_type():
+    return KeyboardButtonPollType(TestKeyboardButtonPollType.type)
+
+
+class TestKeyboardButtonPollType(object):
+    type = Poll.QUIZ
+
+    def test_to_dict(self, keyboard_button_poll_type):
+        keyboard_button_poll_type_dict = keyboard_button_poll_type.to_dict()
+        assert isinstance(keyboard_button_poll_type_dict, dict)
+        assert keyboard_button_poll_type_dict['type'] == self.type
+
+    def test_equality(self):
+        a = KeyboardButtonPollType(Poll.QUIZ)
+        b = KeyboardButtonPollType(Poll.QUIZ)
+        c = KeyboardButtonPollType(Poll.REGULAR)
+
+        assert a == b
+        assert hash(a) == hash(b)
+        assert a is not b
+
+        assert a != c
+        assert hash(a) != hash(c)
diff --git a/tests/test_message.py b/tests/test_message.py
index 6689c459ca1..1262881dc0e 100644
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -90,7 +90,9 @@ def message(bot):
                     {'passport_data': PassportData.de_json(RAW_PASSPORT_DATA, None)},
                     {'poll': Poll(id='abc', question='What is this?',
                                   options=[PollOption(text='a', voter_count=1),
-                                           PollOption(text='b', voter_count=2)], is_closed=False)},
+                                           PollOption(text='b', voter_count=2)], is_closed=False,
+                                  total_voter_count=0, is_anonymous=False, type=Poll.REGULAR,
+                                  allows_multiple_answers=True)},
                     {'text': 'a text message', 'reply_markup': {'inline_keyboard': [[{
                         'text': 'start', 'url': 'http://google.com'}, {
                         'text': 'next', 'callback_data': 'abcd'}],
diff --git a/tests/test_messageentity.py b/tests/test_messageentity.py
index 9cba2596943..62872ab4a58 100644
--- a/tests/test_messageentity.py
+++ b/tests/test_messageentity.py
@@ -32,7 +32,10 @@ def message_entity(request):
     user = None
     if type == MessageEntity.TEXT_MENTION:
         user = User(1, 'test_user', False)
-    return MessageEntity(type, 1, 3, url=url, user=user)
+    language = None
+    if type == MessageEntity.PRE:
+        language = "python"
+    return MessageEntity(type, 1, 3, url=url, user=user, language=language)
 
 
 class TestMessageEntity(object):
@@ -64,6 +67,8 @@ def test_to_dict(self, message_entity):
             assert entity_dict['url'] == message_entity.url
         if message_entity.user:
             assert entity_dict['user'] == message_entity.user.to_dict()
+        if message_entity.language:
+            assert entity_dict['language'] == message_entity.language
 
     def test_equality(self):
         a = MessageEntity(MessageEntity.BOLD, 2, 3)
diff --git a/tests/test_poll.py b/tests/test_poll.py
index bbadf22520f..8502227aed7 100644
--- a/tests/test_poll.py
+++ b/tests/test_poll.py
@@ -19,7 +19,7 @@
 
 import pytest
 
-from telegram import Poll, PollOption
+from telegram import Poll, PollOption, PollAnswer, User
 
 
 @pytest.fixture(scope="class")
@@ -50,26 +50,71 @@ def test_to_dict(self, poll_option):
         assert poll_option_dict['voter_count'] == poll_option.voter_count
 
 
+@pytest.fixture(scope="class")
+def poll_answer():
+    return PollAnswer(poll_id=TestPollAnswer.poll_id, user=TestPollAnswer.user,
+                      option_ids=TestPollAnswer.poll_id)
+
+
+class TestPollAnswer(object):
+    poll_id = 'id'
+    user = User(1, '', False)
+    option_ids = [2]
+
+    def test_de_json(self):
+        json_dict = {
+            'poll_id': self.poll_id,
+            'user': self.user.to_dict(),
+            'option_ids': self.option_ids
+        }
+        poll_answer = PollAnswer.de_json(json_dict, None)
+
+        assert poll_answer.poll_id == self.poll_id
+        assert poll_answer.user == self.user
+        assert poll_answer.option_ids == self.option_ids
+
+    def test_to_dict(self, poll_answer):
+        poll_answer_dict = poll_answer.to_dict()
+
+        assert isinstance(poll_answer_dict, dict)
+        assert poll_answer_dict['poll_id'] == poll_answer.poll_id
+        assert poll_answer_dict['user'] == poll_answer.user.to_dict()
+        assert poll_answer_dict['option_ids'] == poll_answer.option_ids
+
+
 @pytest.fixture(scope='class')
 def poll():
     return Poll(TestPoll.id,
                 TestPoll.question,
                 TestPoll.options,
-                TestPoll.is_closed)
+                TestPoll.total_voter_count,
+                TestPoll.is_closed,
+                TestPoll.is_anonymous,
+                TestPoll.type,
+                TestPoll.allows_multiple_answers
+                )
 
 
 class TestPoll(object):
     id = 'id'
     question = 'Test?'
     options = [PollOption('test', 10), PollOption('test2', 11)]
+    total_voter_count = 0
     is_closed = True
+    is_anonymous = False
+    type = Poll.REGULAR
+    allows_multiple_answers = True
 
     def test_de_json(self):
         json_dict = {
             'id': self.id,
             'question': self.question,
             'options': [o.to_dict() for o in self.options],
-            'is_closed': self.is_closed
+            'total_voter_count': self.total_voter_count,
+            'is_closed': self.is_closed,
+            'is_anonymous': self.is_anonymous,
+            'type': self.type,
+            'allows_multiple_answers': self.allows_multiple_answers
         }
         poll = Poll.de_json(json_dict, None)
 
@@ -80,7 +125,11 @@ def test_de_json(self):
         assert poll.options[0].voter_count == self.options[0].voter_count
         assert poll.options[1].text == self.options[1].text
         assert poll.options[1].voter_count == self.options[1].voter_count
+        assert poll.total_voter_count == self.total_voter_count
         assert poll.is_closed == self.is_closed
+        assert poll.is_anonymous == self.is_anonymous
+        assert poll.type == self.type
+        assert poll.allows_multiple_answers == self.allows_multiple_answers
 
     def test_to_dict(self, poll):
         poll_dict = poll.to_dict()
@@ -89,4 +138,8 @@ def test_to_dict(self, poll):
         assert poll_dict['id'] == poll.id
         assert poll_dict['question'] == poll.question
         assert poll_dict['options'] == [o.to_dict() for o in poll.options]
+        assert poll_dict['total_voter_count'] == poll.total_voter_count
         assert poll_dict['is_closed'] == poll.is_closed
+        assert poll_dict['is_anonymous'] == poll.is_anonymous
+        assert poll_dict['type'] == poll.type
+        assert poll_dict['allows_multiple_answers'] == poll.allows_multiple_answers
diff --git a/tests/test_update.py b/tests/test_update.py
index 59702207a51..15eed9dff6e 100644
--- a/tests/test_update.py
+++ b/tests/test_update.py
@@ -21,6 +21,7 @@
 
 from telegram import (Message, User, Update, Chat, CallbackQuery, InlineQuery,
                       ChosenInlineResult, ShippingQuery, PreCheckoutQuery, Poll, PollOption)
+from telegram.poll import PollAnswer
 
 message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text')
 
@@ -35,12 +36,13 @@
     {'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')},
-    {'poll': Poll('id', '?', [PollOption('.', 1)], False)}
+    {'poll': Poll('id', '?', [PollOption('.', 1)], False, False, False, Poll.REGULAR, True)},
+    {'poll_answer': PollAnswer("id", User(1, '', False), [1])}
 ]
 
 all_types = ('message', 'edited_message', 'callback_query', 'channel_post',
              'edited_channel_post', 'inline_query', 'chosen_inline_result',
-             'shipping_query', 'pre_checkout_query', 'poll')
+             'shipping_query', 'pre_checkout_query', 'poll', 'poll_answer')
 
 ids = all_types + ('callback_query_without_message',)
 
@@ -93,7 +95,8 @@ def test_effective_chat(self, update):
                     and update.callback_query.message is None)
                 or update.shipping_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):
             assert chat.id == 1
         else:
             assert chat is None
@@ -117,7 +120,8 @@ def test_effective_message(self, update):
                     and update.callback_query.message is None)
                 or update.shipping_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):
             assert eff_message.message_id == message.message_id
         else:
             assert eff_message is None
diff --git a/tests/test_user.py b/tests/test_user.py
index cdcdea50f29..065cbbf2f09 100644
--- a/tests/test_user.py
+++ b/tests/test_user.py
@@ -29,7 +29,10 @@ def json_dict():
         'first_name': TestUser.first_name,
         'last_name': TestUser.last_name,
         'username': TestUser.username,
-        'language_code': TestUser.language_code
+        'language_code': TestUser.language_code,
+        'can_join_groups': TestUser.can_join_groups,
+        'can_read_all_group_messages': TestUser.can_read_all_group_messages,
+        'supports_inline_queries': TestUser.supports_inline_queries
     }
 
 
@@ -37,7 +40,9 @@ def json_dict():
 def user(bot):
     return User(id=TestUser.id, first_name=TestUser.first_name, is_bot=TestUser.is_bot,
                 last_name=TestUser.last_name, username=TestUser.username,
-                language_code=TestUser.language_code, bot=bot)
+                language_code=TestUser.language_code, can_join_groups=TestUser.can_join_groups,
+                can_read_all_group_messages=TestUser.can_read_all_group_messages,
+                supports_inline_queries=TestUser.supports_inline_queries, bot=bot)
 
 
 class TestUser(object):
@@ -47,6 +52,9 @@ class TestUser(object):
     last_name = u'last\u2022name'
     username = 'username'
     language_code = 'en_us'
+    can_join_groups = True
+    can_read_all_group_messages = True
+    supports_inline_queries = False
 
     def test_de_json(self, json_dict, bot):
         user = User.de_json(json_dict, bot)
@@ -57,6 +65,9 @@ def test_de_json(self, json_dict, bot):
         assert user.last_name == self.last_name
         assert user.username == self.username
         assert user.language_code == self.language_code
+        assert user.can_join_groups == self.can_join_groups
+        assert user.can_read_all_group_messages == self.can_read_all_group_messages
+        assert user.supports_inline_queries == self.supports_inline_queries
 
     def test_de_json_without_username(self, json_dict, bot):
         del json_dict['username']
@@ -69,6 +80,9 @@ def test_de_json_without_username(self, json_dict, bot):
         assert user.last_name == self.last_name
         assert user.username is None
         assert user.language_code == self.language_code
+        assert user.can_join_groups == self.can_join_groups
+        assert user.can_read_all_group_messages == self.can_read_all_group_messages
+        assert user.supports_inline_queries == self.supports_inline_queries
 
     def test_de_json_without_username_and_last_name(self, json_dict, bot):
         del json_dict['username']
@@ -82,6 +96,9 @@ def test_de_json_without_username_and_last_name(self, json_dict, bot):
         assert user.last_name is None
         assert user.username is None
         assert user.language_code == self.language_code
+        assert user.can_join_groups == self.can_join_groups
+        assert user.can_read_all_group_messages == self.can_read_all_group_messages
+        assert user.supports_inline_queries == self.supports_inline_queries
 
     def test_name(self, user):
         assert user.name == '@username'


Code review comments:
diff_hunk: @@ -739,6 +739,16 @@ def test_update_type_edited_channel_post(self, update):
         assert Filters.update.channel_posts(update)
         assert Filters.update(update)
 
+    def test_update_type_poll(self, update):
+        update.poll = True
path: tests/test_filters.py
text: @user1:
maybe `assert not Filters.update.poll(update)` first?

@author:
Good idea

diff_hunk: @@ -739,6 +739,16 @@ def test_update_type_edited_channel_post(self, update):
         assert Filters.update.channel_posts(update)
         assert Filters.update(update)
 
+    def test_update_type_poll(self, update):
+        update.poll = True
+        assert Filters.update.poll(update)
+        assert Filters.update(update)
path: tests/test_filters.py
text: @user1:
Why does this work? By this [line](https://github.com/python-telegram-bot/python-telegram-bot/blob/d96d233dc5d74a21b800ac970d6d2ef431cc58cb/telegram/ext/filters.py#L1056) I'd expect that only (edited) messages/channel posts come through …

@author:
I override this for poll update type. I think.

@user1:
You override `filter` for the poll type, yes, but `Filters.update(update)` calls `Filters.update.filter()`. But I found the problem: `update` has a `message` attribute (see line 30 above). My suggestion would be:

1. edit `Filters.update.filter` to also allow poll type (i.e. add `or self.poll(update)`)
2. in `test_update_type_poll` and `test_update_type_poll_answer` set `update.message = None` to make sure the poll atrribute is the one getting tested

@author:
do we actually want that though? Or would we prefer a PollHandler? Only message and channel posts are handled in the MessageHandler so far.

@user1:
_If_ we do, here are some thoughts:

* We'd need both PollHandler and PollAnswerHandler
* or/and maybe a keyword poll_answer_updates to choose if PollHandler should also handler PollAnswers
* PollHandler should handle updates with update.message.poll imo
* A pattern argument could filter for poll descriptions