diff --git a/CHANGES.rst b/CHANGES.rst
index 1d5fe1f74..1df7c18ce 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,49 @@
=======
Changes
=======
+
+*Released 11.0.0*
+
+Fully support Bot API version 4.0!
+
+Telegram Passport (`#1174`_):
+
+- Add full support for telegram passport.
+ - New types: PassportData, PassportFile, EncryptedPassportElement, EncryptedCredentials, PassportElementError, PassportElementErrorDataField, PassportElementErrorFrontSide, PassportElementErrorReverseSide, PassportElementErrorSelfie, PassportElementErrorFile and PassportElementErrorFiles.
+ - New bot method: set_passport_data_errors
+ - New filter: Filters.passport_data
+ - Field passport_data field on Message
+ - PassportData is automagically decrypted when you specify your private key when creating Updater or Bot.
+ - PassportFiles is also automagically decrypted as you download/retrieve them.
+- See new passportbot.py example for details on how to use, or go to `our telegram passport wiki page`_ for more info
+- NOTE: Passport decryption requires new dependency `cryptography`.
+
+Inputfile rework (`#1184`_):
+
+- Change how Inputfile is handled internally
+- This allows support for specifying the thumbnails of photos and videos using the thumb= argument in the different send\_ methods.
+- Also allows Bot.send_media_group to actually finally send more than one media.
+- Add thumb to Audio, Video and Videonote
+- Add Bot.edit_message_media together with InputMediaAnimation, InputMediaAudio, and inputMediaDocument.
+
+Other Bot API 4.0 changes:
+
+- Add forusquare_type to Venue, InlineQueryResultVenue, InputVenueMessageContent, and Bot.send_venue. (`#1170`_)
+- Add vCard support by adding vcard field to Contact, InlineQueryResultContact, InputContactMessageContent, and Bot.send_contact. (`#1166`_)
+- Support new message entities: CASHTAG and PHONE_NUMBER. (`#1179`_)
+ - Cashtag seems to be things like `$USD` and `$GBP`, but it seems telegram doesn't currently send them to bots.
+ - Phone number also seems to have limited support for now
+- Add Bot.send_animation, add width, height, and duration to Animation, and add Filters.animation. (`#1172`_)
+
+
+.. _`#1174`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1174
+.. _`#1184`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1184
+.. _`#1170`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1170
+.. _`#1166`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1166
+.. _`#1179`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1179
+.. _`#1172`: https://github.com/python-telegram-bot/python-telegram-bot/pull/1172
+.. _`our telegram passport wiki page`: https://git.io/fAvYd
+
**2018-05-02**
*Released 10.1.0*
diff --git a/README.rst b/README.rst
index 805014273..111043fb0 100644
--- a/README.rst
+++ b/README.rst
@@ -95,7 +95,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 3.6 are supported.
+All types and methods of the Telegram Bot API **4.0** are supported.
==========
Installing
diff --git a/docs/source/telegram.credentials.rst b/docs/source/telegram.credentials.rst
new file mode 100644
index 000000000..6a3fe39e1
--- /dev/null
+++ b/docs/source/telegram.credentials.rst
@@ -0,0 +1,6 @@
+telegram.Credentials
+====================
+
+.. autoclass:: telegram.Credentials
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.datacredentials.rst b/docs/source/telegram.datacredentials.rst
new file mode 100644
index 000000000..b0916762e
--- /dev/null
+++ b/docs/source/telegram.datacredentials.rst
@@ -0,0 +1,6 @@
+telegram.DataCredentials
+========================
+
+.. autoclass:: telegram.DataCredentials
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.encryptedcredentials.rst b/docs/source/telegram.encryptedcredentials.rst
new file mode 100644
index 000000000..d42851436
--- /dev/null
+++ b/docs/source/telegram.encryptedcredentials.rst
@@ -0,0 +1,6 @@
+telegram.EncryptedCredentials
+=============================
+
+.. autoclass:: telegram.EncryptedCredentials
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.encryptedpassportelement.rst b/docs/source/telegram.encryptedpassportelement.rst
new file mode 100644
index 000000000..f22963601
--- /dev/null
+++ b/docs/source/telegram.encryptedpassportelement.rst
@@ -0,0 +1,6 @@
+telegram.EncryptedPassportElement
+=================================
+
+.. autoclass:: telegram.EncryptedPassportElement
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.filecredentials.rst b/docs/source/telegram.filecredentials.rst
new file mode 100644
index 000000000..600661e50
--- /dev/null
+++ b/docs/source/telegram.filecredentials.rst
@@ -0,0 +1,6 @@
+telegram.FileCredentials
+========================
+
+.. autoclass:: telegram.FileCredentials
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.iddocumentdata.rst b/docs/source/telegram.iddocumentdata.rst
new file mode 100644
index 000000000..1333f7ed4
--- /dev/null
+++ b/docs/source/telegram.iddocumentdata.rst
@@ -0,0 +1,6 @@
+telegram.IdDocumentData
+=======================
+
+.. autoclass:: telegram.IdDocumentData
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.inputmediaanimation.rst b/docs/source/telegram.inputmediaanimation.rst
new file mode 100644
index 000000000..b94711b06
--- /dev/null
+++ b/docs/source/telegram.inputmediaanimation.rst
@@ -0,0 +1,6 @@
+telegram.InputMediaAnimation
+============================
+
+.. autoclass:: telegram.InputMediaAnimation
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.inputmediaaudio.rst b/docs/source/telegram.inputmediaaudio.rst
new file mode 100644
index 000000000..5a7a977fc
--- /dev/null
+++ b/docs/source/telegram.inputmediaaudio.rst
@@ -0,0 +1,6 @@
+telegram.InputMediaAudio
+========================
+
+.. autoclass:: telegram.InputMediaAudio
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.inputmediadocument.rst b/docs/source/telegram.inputmediadocument.rst
new file mode 100644
index 000000000..746b15f9a
--- /dev/null
+++ b/docs/source/telegram.inputmediadocument.rst
@@ -0,0 +1,6 @@
+telegram.InputMediaDocument
+===========================
+
+.. autoclass:: telegram.InputMediaDocument
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.passportdata.rst b/docs/source/telegram.passportdata.rst
new file mode 100644
index 000000000..43a33b560
--- /dev/null
+++ b/docs/source/telegram.passportdata.rst
@@ -0,0 +1,6 @@
+telegram.PassportData
+=====================
+
+.. autoclass:: telegram.PassportData
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.passportelementerror.rst b/docs/source/telegram.passportelementerror.rst
new file mode 100644
index 000000000..00ef430d7
--- /dev/null
+++ b/docs/source/telegram.passportelementerror.rst
@@ -0,0 +1,6 @@
+telegram.PassportElementError
+=============================
+
+.. autoclass:: telegram.PassportElementError
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.passportelementerrordatafield.rst b/docs/source/telegram.passportelementerrordatafield.rst
new file mode 100644
index 000000000..ac304f7b5
--- /dev/null
+++ b/docs/source/telegram.passportelementerrordatafield.rst
@@ -0,0 +1,6 @@
+telegram.PassportElementErrorDataField
+======================================
+
+.. autoclass:: telegram.PassportElementErrorDataField
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.passportelementerrorfile.rst b/docs/source/telegram.passportelementerrorfile.rst
new file mode 100644
index 000000000..339d7a786
--- /dev/null
+++ b/docs/source/telegram.passportelementerrorfile.rst
@@ -0,0 +1,6 @@
+telegram.PassportElementErrorFile
+=================================
+
+.. autoclass:: telegram.PassportElementErrorFile
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.passportelementerrorfiles.rst b/docs/source/telegram.passportelementerrorfiles.rst
new file mode 100644
index 000000000..f9643d969
--- /dev/null
+++ b/docs/source/telegram.passportelementerrorfiles.rst
@@ -0,0 +1,6 @@
+telegram.PassportElementErrorFiles
+==================================
+
+.. autoclass:: telegram.PassportElementErrorFiles
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.passportelementerrorfrontside.rst b/docs/source/telegram.passportelementerrorfrontside.rst
new file mode 100644
index 000000000..a92635a27
--- /dev/null
+++ b/docs/source/telegram.passportelementerrorfrontside.rst
@@ -0,0 +1,6 @@
+telegram.PassportElementErrorFrontSide
+======================================
+
+.. autoclass:: telegram.PassportElementErrorFrontSide
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.passportelementerrorreverseside.rst b/docs/source/telegram.passportelementerrorreverseside.rst
new file mode 100644
index 000000000..380a10664
--- /dev/null
+++ b/docs/source/telegram.passportelementerrorreverseside.rst
@@ -0,0 +1,6 @@
+telegram.PassportElementErrorReverseSide
+========================================
+
+.. autoclass:: telegram.PassportElementErrorReverseSide
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.passportfile.rst b/docs/source/telegram.passportfile.rst
new file mode 100644
index 000000000..a2af1c5f6
--- /dev/null
+++ b/docs/source/telegram.passportfile.rst
@@ -0,0 +1,6 @@
+telegram.PassportFile
+=====================
+
+.. autoclass:: telegram.PassportFile
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.personaldetails.rst b/docs/source/telegram.personaldetails.rst
new file mode 100644
index 000000000..145769f23
--- /dev/null
+++ b/docs/source/telegram.personaldetails.rst
@@ -0,0 +1,6 @@
+telegram.PersonalDetails
+========================
+
+.. autoclass:: telegram.PersonalDetails
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.residentialaddress.rst b/docs/source/telegram.residentialaddress.rst
new file mode 100644
index 000000000..95be56963
--- /dev/null
+++ b/docs/source/telegram.residentialaddress.rst
@@ -0,0 +1,6 @@
+telegram.ResidentialAddress
+===========================
+
+.. autoclass:: telegram.ResidentialAddress
+ :members:
+ :show-inheritance:
diff --git a/docs/source/telegram.rst b/docs/source/telegram.rst
index 2c2b4ecdf..1b19b670d 100644
--- a/docs/source/telegram.rst
+++ b/docs/source/telegram.rst
@@ -5,6 +5,7 @@ telegram package
telegram.ext
telegram.utils
+ telegram.animation
telegram.audio
telegram.bot
telegram.callbackquery
@@ -22,6 +23,9 @@ telegram package
telegram.inlinekeyboardmarkup
telegram.inputfile
telegram.inputmedia
+ telegram.inputmediaanimation
+ telegram.inputmediaaudio
+ telegram.inputmediadocument
telegram.inputmediaphoto
telegram.inputmediavideo
telegram.keyboardbutton
@@ -106,10 +110,33 @@ Games
.. toctree::
telegram.game
- telegram.animation
telegram.callbackgame
telegram.gamehighscore
+Passport
+--------
+
+.. toctree::
+
+ telegram.passportelementerror
+ telegram.passportelementerrorfile
+ telegram.passportelementerrorreverseside
+ telegram.passportelementerrorfrontside
+ telegram.passportelementerrorfiles
+ telegram.passportelementerrordatafield
+ telegram.passportelementerrorfile
+ telegram.credentials
+ telegram.datacredentials
+ telegram.securedata
+ telegram.filecredentials
+ telegram.iddocumentdata
+ telegram.personaldetails
+ telegram.residentialaddress
+ telegram.passportdata
+ telegram.passportfile
+ telegram.encryptedpassportelement
+ telegram.encryptedcredentials
+
Module contents
---------------
diff --git a/docs/source/telegram.securedata.rst b/docs/source/telegram.securedata.rst
new file mode 100644
index 000000000..ec24c8c4c
--- /dev/null
+++ b/docs/source/telegram.securedata.rst
@@ -0,0 +1,6 @@
+telegram.SecureData
+===================
+
+.. autoclass:: telegram.SecureData
+ :members:
+ :show-inheritance:
diff --git a/examples/passportbot.html b/examples/passportbot.html
new file mode 100644
index 000000000..3b2c4a4e9
--- /dev/null
+++ b/examples/passportbot.html
@@ -0,0 +1,29 @@
+
+
+
+ Telegram passport test!
+
+
+
+
+
+
+
+
+
+Telegram passport test
+
+
+
+
diff --git a/examples/passportbot.py b/examples/passportbot.py
new file mode 100644
index 000000000..f9a239c5d
--- /dev/null
+++ b/examples/passportbot.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Simple Bot to print/download all incoming passport data
+# This program is dedicated to the public domain under the CC0 license.
+"""
+See https://telegram.org/blog/passport for info about what telegram passport is.
+
+See https://git.io/fAvYd for how to use Telegram Passport properly with python-telegram-bot.
+
+"""
+import logging
+
+from telegram.ext import Updater, MessageHandler, Filters
+
+# Enable logging
+logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.DEBUG)
+
+logger = logging.getLogger(__name__)
+
+
+def msg(bot, update):
+ # If we received any passport data
+ passport_data = update.message.passport_data
+ if passport_data:
+ # If our payload doesn't match what we think, this Update did not originate from us
+ # Ideally you would randomize the payload on the server
+ if passport_data.decrypted_credentials.payload != 'thisisatest':
+ return
+
+ # Print the decrypted credential data
+ # For all elements
+ # Print their decrypted data
+ # Files will be downloaded to current directory
+ for data in passport_data.decrypted_data: # This is where the data gets decrypted
+ if data.type == 'phone_number':
+ print('Phone: ', data.phone_number)
+ elif data.type == 'email':
+ print('Email: ', data.email)
+ if data.type in ('personal_details', 'passport', 'driver_license', 'identity_card',
+ 'identity_passport', 'address'):
+ print(data.type, data.data)
+ if data.type in ('utility_bill', 'bank_statement', 'rental_agreement',
+ 'passport_registration', 'temporary_registration'):
+ print(data.type, len(data.files), 'files')
+ for file in data.files:
+ actual_file = file.get_file()
+ print(actual_file)
+ actual_file.download()
+ if data.type in ('passport', 'driver_license', 'identity_card',
+ 'internal_passport'):
+ if data.front_side:
+ file = data.front_side.get_file()
+ print(data.type, file)
+ file.download()
+ if data.type in ('driver_license' and 'identity_card'):
+ if data.reverse_side:
+ file = data.reverse_side.get_file()
+ print(data.type, file)
+ file.download()
+ if data.type in ('passport', 'driver_license', 'identity_card',
+ 'internal_passport'):
+ if data.selfie:
+ file = data.selfie.get_file()
+ print(data.type, file)
+ file.download()
+
+
+def error(bot, update, error):
+ """Log Errors caused by Updates."""
+ logger.warning('Update "%s" caused error "%s"', update, error)
+
+
+def main():
+ """Start the bot."""
+ # Create the Updater and pass it your token and private key
+ updater = Updater("TOKEN", private_key=open('private.key', 'rb').read())
+
+ # Get the dispatcher to register handlers
+ dp = updater.dispatcher
+
+ # On messages that include passport data call msg
+ dp.add_handler(MessageHandler(Filters.passport_data, msg))
+
+ # log all errors
+ dp.add_error_handler(error)
+
+ # Start the Bot
+ updater.start_polling()
+
+ # Run the bot until you press Ctrl-C or the process receives SIGINT,
+ # SIGTERM or SIGABRT. This should be used most of the time, since
+ # start_polling() is non-blocking and will stop the bot gracefully.
+ updater.idle()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/requirements.txt b/requirements.txt
index 24c2ec399..e500246b6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
future>=0.16.0
certifi
+cryptography
diff --git a/telegram/__init__.py b/telegram/__init__.py
index ae2ca2570..cde4547ff 100644
--- a/telegram/__init__.py
+++ b/telegram/__init__.py
@@ -27,6 +27,7 @@ from .files.photosize import PhotoSize
from .files.audio import Audio
from .files.voice import Voice
from .files.document import Document
+from .files.animation import Animation
from .files.sticker import Sticker, StickerSet, MaskPosition
from .files.video import Video
from .files.contact import Contact
@@ -45,13 +46,17 @@ from .files.inputfile import InputFile
from .files.file import File
from .parsemode import ParseMode
from .messageentity import MessageEntity
-from .games.animation import Animation
from .games.game import Game
from .games.callbackgame import CallbackGame
from .payment.shippingaddress import ShippingAddress
from .payment.orderinfo import OrderInfo
from .payment.successfulpayment import SuccessfulPayment
from .payment.invoice import Invoice
+from .passport.credentials import EncryptedCredentials
+from .passport.passportfile import PassportFile
+from .passport.data import IdDocumentData, PersonalDetails, ResidentialAddress
+from .passport.encryptedpassportelement import EncryptedPassportElement
+from .passport.passportdata import PassportData
from .message import Message
from .callbackquery import CallbackQuery
from .choseninlineresult import ChosenInlineResult
@@ -91,14 +96,25 @@ from .payment.shippingquery import ShippingQuery
from .webhookinfo import WebhookInfo
from .games.gamehighscore import GameHighScore
from .update import Update
+from .files.inputmedia import (InputMedia, InputMediaVideo, InputMediaPhoto, InputMediaAnimation,
+ InputMediaAudio, InputMediaDocument)
from .bot import Bot
from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOOK_PORTS,
MAX_FILESIZE_DOWNLOAD, MAX_FILESIZE_UPLOAD,
MAX_MESSAGES_PER_SECOND_PER_CHAT, MAX_MESSAGES_PER_SECOND,
MAX_MESSAGES_PER_MINUTE_PER_GROUP)
-from .files.inputmedia import InputMedia
-from .files.inputmediavideo import InputMediaVideo
-from .files.inputmediaphoto import InputMediaPhoto
+from .passport.passportelementerrors import (PassportElementError,
+ PassportElementErrorDataField,
+ PassportElementErrorFile,
+ PassportElementErrorFiles,
+ PassportElementErrorFrontSide,
+ PassportElementErrorReverseSide,
+ PassportElementErrorSelfie)
+from .passport.credentials import (Credentials,
+ DataCredentials,
+ SecureData,
+ FileCredentials,
+ TelegramDecryptionError)
from .version import __version__ # flake8: noqa
__author__ = 'devs@python-telegram-bot.org'
@@ -116,7 +132,8 @@ __all__ = [
'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo',
'InlineQueryResultVoice', 'InlineQueryResultGame', 'InputContactMessageContent', 'InputFile',
'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent',
- 'InputVenueMessageContent', 'KeyboardButton', 'Location', 'Message', 'MessageEntity',
+ 'InputVenueMessageContent', 'KeyboardButton', 'Location', 'EncryptedCredentials',
+ 'PassportFile', 'EncryptedPassportElement', 'PassportData', 'Message', 'MessageEntity',
'ParseMode', 'PhotoSize', 'ReplyKeyboardRemove', 'ReplyKeyboardMarkup', 'ReplyMarkup',
'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
'Video', 'Voice', 'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS',
@@ -125,5 +142,11 @@ __all__ = [
'Game', 'GameHighScore', 'VideoNote', 'LabeledPrice', 'SuccessfulPayment', 'ShippingOption',
'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery', 'ChatPhoto',
'StickerSet', 'MaskPosition', 'CallbackGame', 'InputMedia', 'InputMediaPhoto',
- 'InputMediaVideo'
+ 'InputMediaVideo', 'PassportElementError', 'PassportElementErrorFile',
+ 'PassportElementErrorReverseSide', 'PassportElementErrorFrontSide',
+ 'PassportElementErrorFiles', 'PassportElementErrorDataField', 'PassportElementErrorFile',
+ 'Credentials', 'DataCredentials', 'SecureData', 'FileCredentials', 'IdDocumentData',
+ 'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation',
+ 'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
+ 'PassportElementErrorSelfie'
]
diff --git a/telegram/base.py b/telegram/base.py
index b8b959eab..80259ac87 100644
--- a/telegram/base.py
+++ b/telegram/base.py
@@ -60,7 +60,12 @@ class TelegramObject(object):
data = dict()
for key in iter(self.__dict__):
- if key in ('bot', '_id_attrs'):
+ if key in ('bot',
+ '_id_attrs',
+ '_credentials',
+ '_decrypted_credentials',
+ '_decrypted_data',
+ '_decrypted_secret'):
continue
value = self.__dict__[key]
diff --git a/telegram/bot.py b/telegram/bot.py
index 0801303dc..6aed87118 100644
--- a/telegram/bot.py
+++ b/telegram/bot.py
@@ -21,17 +21,22 @@
"""This module contains an object that represents a Telegram Bot."""
import functools
-import json
+try:
+ import ujson as json
+except ImportError:
+ import json
import logging
import warnings
from datetime import datetime
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
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, Voice, VideoNote, Location,
- Venue, Contact)
+ PhotoSize, Audio, Document, Sticker, Video, Animation, Voice, VideoNote,
+ Location, Venue, Contact, InputFile)
from telegram.error import InvalidToken, TelegramError
from telegram.utils.helpers import to_timestamp
from telegram.utils.request import Request
@@ -101,10 +106,13 @@ class Bot(TelegramObject):
base_file_url (:obj:`str`, optional): Telegram Bot API file URL.
request (:obj:`telegram.utils.request.Request`, optional): Pre initialized
:obj:`telegram.utils.request.Request`.
+ private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
+ private_key_password (:obj:`bytes`, optional): Password for above private key.
"""
- def __init__(self, token, base_url=None, base_file_url=None, request=None):
+ def __init__(self, token, base_url=None, base_file_url=None, request=None, private_key=None,
+ private_key_password=None):
self.token = self._validate_token(token)
if base_url is None:
@@ -119,6 +127,11 @@ class Bot(TelegramObject):
self._request = request or Request()
self.logger = logging.getLogger(__name__)
+ if private_key:
+ self.private_key = serialization.load_pem_private_key(private_key,
+ password=private_key_password,
+ backend=default_backend())
+
@property
def request(self):
return self._request
@@ -381,6 +394,8 @@ class Bot(TelegramObject):
if isinstance(photo, PhotoSize):
photo = photo.file_id
+ elif InputFile.is_file(photo):
+ photo = InputFile(photo)
data = {'chat_id': chat_id, 'photo': photo}
@@ -405,6 +420,7 @@ class Bot(TelegramObject):
reply_markup=None,
timeout=20,
parse_mode=None,
+ thumb=None,
**kwargs):
"""
Use this method to send audio files, if you want Telegram clients to display them in the
@@ -440,6 +456,10 @@ class Bot(TelegramObject):
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.
+ thumb (`filelike object`, optional): Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
@@ -454,6 +474,8 @@ class Bot(TelegramObject):
if isinstance(audio, Audio):
audio = audio.file_id
+ elif InputFile.is_file(audio):
+ audio = InputFile(audio)
data = {'chat_id': chat_id, 'audio': audio}
@@ -467,6 +489,10 @@ class Bot(TelegramObject):
data['caption'] = caption
if parse_mode:
data['parse_mode'] = parse_mode
+ if thumb:
+ if InputFile.is_file(thumb):
+ thumb = InputFile(thumb, attach=True)
+ data['thumb'] = thumb
return url, data
@@ -482,6 +508,7 @@ class Bot(TelegramObject):
reply_markup=None,
timeout=20,
parse_mode=None,
+ thumb=None,
**kwargs):
"""Use this method to send general files.
@@ -511,6 +538,10 @@ class Bot(TelegramObject):
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.
+ thumb (`filelike object`, optional): Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
@@ -525,15 +556,19 @@ class Bot(TelegramObject):
if isinstance(document, Document):
document = document.file_id
+ elif InputFile.is_file(document):
+ document = InputFile(document, filename=filename)
data = {'chat_id': chat_id, 'document': document}
- if filename:
- data['filename'] = filename
if caption:
data['caption'] = caption
if parse_mode:
data['parse_mode'] = parse_mode
+ if thumb:
+ if InputFile.is_file(thumb):
+ thumb = InputFile(thumb, attach=True)
+ data['thumb'] = thumb
return url, data
@@ -582,6 +617,8 @@ class Bot(TelegramObject):
if isinstance(sticker, Sticker):
sticker = sticker.file_id
+ elif InputFile.is_file(sticker):
+ sticker = InputFile(sticker)
data = {'chat_id': chat_id, 'sticker': sticker}
@@ -602,6 +639,7 @@ class Bot(TelegramObject):
height=None,
parse_mode=None,
supports_streaming=None,
+ thumb=None,
**kwargs):
"""
Use this method to send video files, Telegram clients support mp4 videos
@@ -636,6 +674,10 @@ class Bot(TelegramObject):
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.
+ thumb (`filelike object`, optional): Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
@@ -650,6 +692,8 @@ class Bot(TelegramObject):
if isinstance(video, Video):
video = video.file_id
+ elif InputFile.is_file(video):
+ video = InputFile(video)
data = {'chat_id': chat_id, 'video': video}
@@ -665,6 +709,162 @@ class Bot(TelegramObject):
data['width'] = width
if height:
data['height'] = height
+ if thumb:
+ if InputFile.is_file(thumb):
+ thumb = InputFile(thumb, attach=True)
+ data['thumb'] = thumb
+
+ return url, data
+
+ @log
+ @message
+ def send_video_note(self,
+ chat_id,
+ video_note,
+ duration=None,
+ length=None,
+ disable_notification=False,
+ reply_to_message_id=None,
+ reply_markup=None,
+ timeout=20,
+ thumb=None,
+ **kwargs):
+ """Use this method to send video messages.
+
+ Note:
+ The video_note argument can be either a file_id or a file from disk
+ ``open(filename, 'rb')``
+
+ Args:
+ chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
+ of the target channel (in the format @channelusername).
+ video_note (:obj:`str` | `filelike object` | :class:`telegram.VideoNote`): Video note
+ to send. Pass a file_id as String to send a video note that exists on the Telegram
+ servers (recommended) or upload a new video using multipart/form-data. Or you can
+ pass an existing :class:`telegram.VideoNote` object to send. Sending video notes by
+ a URL is currently unsupported.
+ duration (:obj:`int`, optional): Duration of sent video in seconds.
+ length (:obj:`int`, optional): Video width and height
+ 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.
+ thumb (`filelike object`, optional): Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+ timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ Returns:
+ :class:`telegram.Message`: On success, the sent Message is returned.
+
+ Raises:
+ :class:`telegram.TelegramError`
+
+ """
+ url = '{0}/sendVideoNote'.format(self.base_url)
+
+ if isinstance(video_note, VideoNote):
+ video_note = video_note.file_id
+ elif InputFile.is_file(video_note):
+ video_note = InputFile(video_note)
+
+ data = {'chat_id': chat_id, 'video_note': video_note}
+
+ if duration is not None:
+ data['duration'] = duration
+ if length is not None:
+ data['length'] = length
+ if thumb:
+ if InputFile.is_file(thumb):
+ thumb = InputFile(thumb, attach=True)
+ data['thumb'] = thumb
+
+ return url, data
+
+ @log
+ @message
+ def send_animation(self,
+ chat_id,
+ animation,
+ duration=None,
+ width=None,
+ height=None,
+ thumb=None,
+ caption=None,
+ parse_mode=None,
+ disable_notification=False,
+ reply_to_message_id=None,
+ reply_markup=None,
+ timeout=20,
+ **kwargs):
+ """
+ Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound).
+
+ Args:
+ chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
+ of the target channel (in the format @channelusername).
+ animation (:obj:`str` | `filelike object` | :class:`telegram.Animation`): Animation to
+ send. Pass a file_id as String to send an animation that exists on the Telegram
+ servers (recommended), pass an HTTP URL as a String for Telegram to get an
+ animation from the Internet, or upload a new animation using multipart/form-data.
+ Lastly you can pass an existing :class:`telegram.Animation` object to send.
+ duration (:obj:`int`, optional): Duration of sent animation in seconds.
+ width (:obj:`int`, optional): Animation width.
+ height (:obj:`int`, optional): Animation height.
+ thumb (`filelike object`, optional): Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+ caption (:obj:`str`, optional): Animation caption (may also be used when resending
+ animations by file_id), 0-200 characters.
+ parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to
+ show bold, italic, fixed-width text or inline URLs in the media caption. See the
+ constants in :class:`telegram.ParseMode` for the available modes.
+ 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): Send file timeout (default: 20 seconds).
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ Returns:
+ :class:`telegram.Message`: On success, the sent Message is returned.
+
+ Raises:
+ :class:`telegram.TelegramError`
+
+ """
+ url = '{0}/sendAnimation'.format(self.base_url)
+
+ if isinstance(animation, Animation):
+ animation = animation.file_id
+ elif InputFile.is_file(animation):
+ animation = InputFile(animation)
+
+ data = {'chat_id': chat_id, 'animation': animation}
+
+ if duration:
+ data['duration'] = duration
+ if width:
+ data['width'] = width
+ if height:
+ data['height'] = height
+ if thumb:
+ if InputFile.is_file(thumb):
+ thumb = InputFile(thumb, attach=True)
+ data['thumb'] = thumb
+ if caption:
+ data['caption'] = caption
+ if parse_mode:
+ data['parse_mode'] = parse_mode
return url, data
@@ -724,6 +924,8 @@ class Bot(TelegramObject):
if isinstance(voice, Voice):
voice = voice.file_id
+ elif InputFile.is_file(voice):
+ voice = InputFile(voice)
data = {'chat_id': chat_id, 'voice': voice}
@@ -736,65 +938,6 @@ class Bot(TelegramObject):
return url, data
- @log
- @message
- def send_video_note(self,
- chat_id,
- video_note,
- duration=None,
- length=None,
- disable_notification=False,
- reply_to_message_id=None,
- reply_markup=None,
- timeout=20,
- **kwargs):
- """Use this method to send video messages.
-
- Note:
- The video_note argument can be either a file_id or a file from disk
- ``open(filename, 'rb')``
-
- Args:
- chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
- of the target channel (in the format @channelusername).
- video_note (:obj:`str` | `filelike object` | :class:`telegram.VideoNote`): Video note
- to send. Pass a file_id as String to send a video note that exists on the Telegram
- servers (recommended) or upload a new video using multipart/form-data. Or you can
- pass an existing :class:`telegram.VideoNote` object to send. Sending video notes by
- a URL is currently unsupported.
- duration (:obj:`int`, optional): Duration of sent video in seconds.
- length (:obj:`int`, optional): Video width and height
- 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): Send file timeout (default: 20 seconds).
- **kwargs (:obj:`dict`): Arbitrary keyword arguments.
-
- Returns:
- :class:`telegram.Message`: On success, the sent Message is returned.
-
- Raises:
- :class:`telegram.TelegramError`
-
- """
- url = '{0}/sendVideoNote'.format(self.base_url)
-
- if isinstance(video_note, VideoNote):
- video_note = video_note.file_id
-
- data = {'chat_id': chat_id, 'video_note': video_note}
-
- if duration is not None:
- data['duration'] = duration
- if length is not None:
- data['length'] = length
-
- return url, data
-
@log
def send_media_group(self,
chat_id,
@@ -823,12 +966,9 @@ class Bot(TelegramObject):
Raises:
:class:`telegram.TelegramError`
"""
- # TODO: Make InputMediaPhoto, InputMediaVideo and send_media_group work with new files
url = '{0}/sendMediaGroup'.format(self.base_url)
- media = [med.to_dict() for med in media]
-
data = {'chat_id': chat_id, 'media': media}
if reply_to_message_id:
@@ -1025,12 +1165,14 @@ class Bot(TelegramObject):
reply_markup=None,
timeout=None,
venue=None,
+ foursquare_type=None,
**kwargs):
"""Use this method to send information about a venue.
Note:
you can either supply :obj:`venue`, or :obj:`latitude`, :obj:`longitude`,
- :obj:`title` and :obj:`address` and optionally :obj:`foursquare_id`.
+ :obj:`title` and :obj:`address` and optionally :obj:`foursquare_id` and optionally
+ :obj:`foursquare_type`.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
@@ -1040,6 +1182,9 @@ class Bot(TelegramObject):
title (:obj:`str`, optional): Name of the venue.
address (:obj:`str`, optional): Address of the venue.
foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue.
+ foursquare_type (:obj:`str`, optional): Foursquare type of the venue, if known.
+ (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or
+ "food/icecream".)
venue (:class:`telegram.Venue`, optional): The venue to send.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound.
@@ -1072,6 +1217,7 @@ class Bot(TelegramObject):
address = venue.address
title = venue.title
foursquare_id = venue.foursquare_id
+ foursquare_type = venue.foursquare_type
data = {
'chat_id': chat_id,
@@ -1083,6 +1229,8 @@ class Bot(TelegramObject):
if foursquare_id:
data['foursquare_id'] = foursquare_id
+ if foursquare_type:
+ data['foursquare_type'] = foursquare_type
return url, data
@@ -1098,12 +1246,13 @@ class Bot(TelegramObject):
reply_markup=None,
timeout=None,
contact=None,
+ vcard=None,
**kwargs):
"""Use this method to send phone contacts.
Note:
You can either supply :obj:`contact` or :obj:`phone_number` and :obj:`first_name`
- with optionally :obj:`last_name`.
+ with optionally :obj:`last_name` and optionally :obj:`vcard`.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
@@ -1111,6 +1260,8 @@ class Bot(TelegramObject):
phone_number (:obj:`str`, optional): Contact's phone number.
first_name (:obj:`str`, optional): Contact's first name.
last_name (:obj:`str`, optional): Contact's last name.
+ vcard (:obj:`str`, optional): Additional data about the contact in the form of a vCard,
+ 0-2048 bytes.
contact (:class:`telegram.Contact`, optional): The contact to send.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound.
@@ -1141,11 +1292,14 @@ class Bot(TelegramObject):
phone_number = contact.phone_number
first_name = contact.first_name
last_name = contact.last_name
+ vcard = contact.vcard
data = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name}
if last_name:
data['last_name'] = last_name
+ if vcard:
+ data['vcard'] = vcard
return url, data
@@ -1648,6 +1802,59 @@ class Bot(TelegramObject):
return url, data
+ @log
+ @message
+ def edit_message_media(self,
+ chat_id=None,
+ message_id=None,
+ inline_message_id=None,
+ media=None,
+ reply_markup=None,
+ timeout=None,
+ **kwargs):
+ """Use this method to edit audio, document, photo, or video messages. If a message is a
+ part of a message album, then it can be edited only to a photo or a video. Otherwise,
+ message type can be changed arbitrarily. When inline message is edited, new file can't be
+ uploaded. Use previously uploaded file via its file_id or specify a URL. On success, if the
+ edited message was sent by the bot, the edited Message is returned, otherwise True is
+ returned.
+
+ Args:
+ chat_id (:obj:`int` | :obj:`str`, optional): Unique identifier for the target chat or
+ username of the target`channel (in the format @channelusername).
+ message_id (:obj:`int`, optional): Required if inline_message_id is not specified.
+ Identifier of the sent message.
+ inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not
+ specified. Identifier of the inline message.
+ media (:class:`telegram.InputMedia`): An object for a new media content
+ of the 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.
+ """
+
+ if inline_message_id is None and (chat_id is None or message_id is None):
+ raise ValueError(
+ 'edit_message_caption: Both chat_id and message_id are required when '
+ 'inline_message_id is not specified')
+
+ url = '{0}/editMessageMedia'.format(self.base_url)
+
+ data = {'media': media}
+
+ if chat_id:
+ data['chat_id'] = chat_id
+ if message_id:
+ data['message_id'] = message_id
+ if inline_message_id:
+ data['inline_message_id'] = inline_message_id
+
+ return url, data
+
@log
@message
def edit_message_reply_markup(self,
@@ -1851,6 +2058,8 @@ class Bot(TelegramObject):
if url is not None:
data['url'] = url
if certificate:
+ if InputFile.is_file(certificate):
+ certificate = InputFile(certificate)
data['certificate'] = certificate
if max_connections is not None:
data['max_connections'] = max_connections
@@ -2658,6 +2867,9 @@ class Bot(TelegramObject):
"""
url = '{0}/setChatPhoto'.format(self.base_url)
+ if InputFile.is_file(photo):
+ photo = InputFile(photo)
+
data = {'chat_id': chat_id, 'photo': photo}
data.update(kwargs)
@@ -2892,6 +3104,9 @@ class Bot(TelegramObject):
"""
url = '{0}/uploadStickerFile'.format(self.base_url)
+ if InputFile.is_file(png_sticker):
+ png_sticker = InputFile(png_sticker)
+
data = {'user_id': user_id, 'png_sticker': png_sticker}
data.update(kwargs)
@@ -2943,6 +3158,9 @@ class Bot(TelegramObject):
"""
url = '{0}/createNewStickerSet'.format(self.base_url)
+ 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}
@@ -2991,6 +3209,9 @@ class Bot(TelegramObject):
"""
url = '{0}/addStickerToSet'.format(self.base_url)
+ 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 mask_position is not None:
@@ -3056,6 +3277,44 @@ class Bot(TelegramObject):
return result
+ @log
+ def set_passport_data_errors(self, user_id, errors, timeout=None, **kwargs):
+ """
+ Informs a user that some of the Telegram Passport elements they provided contains errors.
+ The user will not be able to re-submit their Passport to you until the errors are fixed
+ (the contents of the field for which you returned the error must change). Returns True
+ on success.
+
+ Use this if the data submitted by the user doesn't satisfy the standards your service
+ requires for any reason. For example, if a birthday date seems invalid, a submitted
+ document is blurry, a scan shows evidence of tampering, etc. Supply some details in the
+ error message to make sure the user knows how to correct the issues.
+
+ Args:
+ user_id (:obj:`int`): User identifier
+ errors (List[:class:`PassportElementError`]): A JSON-serialized array describing the
+ errors.
+ 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}/setPassportDataErrors'.format(self.base_url)
+
+ data = {'user_id': user_id, 'errors': [error.to_dict() for error in errors]}
+ data.update(kwargs)
+
+ result = self._request.post(url_, data, timeout=timeout)
+
+ return result
+
def to_dict(self):
data = {'id': self.id, 'username': self.username, 'first_name': self.username}
@@ -3087,6 +3346,8 @@ class Bot(TelegramObject):
"""Alias for :attr:`send_sticker`"""
sendVideo = send_video
"""Alias for :attr:`send_video`"""
+ sendAnimation = send_animation
+ """Alias for :attr:`send_animation`"""
sendVoice = send_voice
"""Alias for :attr:`send_voice`"""
sendVideoNote = send_video_note
@@ -3123,6 +3384,8 @@ class Bot(TelegramObject):
"""Alias for :attr:`edit_message_text`"""
editMessageCaption = edit_message_caption
"""Alias for :attr:`edit_message_caption`"""
+ editMessageMedia = edit_message_media
+ """Alias for :attr:`edit_message_media`"""
editMessageReplyMarkup = edit_message_reply_markup
"""Alias for :attr:`edit_message_reply_markup`"""
getUpdates = get_updates
@@ -3187,3 +3450,5 @@ class Bot(TelegramObject):
"""Alias for :attr:`set_sticker_position_in_set`"""
deleteStickerFromSet = delete_sticker_from_set
"""Alias for :attr:`delete_sticker_from_set`"""
+ setPassportDataErrors = set_passport_data_errors
+ """Alias for :attr:`set_passport_data_errors`"""
diff --git a/telegram/chat.py b/telegram/chat.py
index d5bc3da2b..5938c7c14 100644
--- a/telegram/chat.py
+++ b/telegram/chat.py
@@ -273,6 +273,19 @@ class Chat(TelegramObject):
"""
return self.bot.send_document(self.id, *args, **kwargs)
+ def send_animation(self, *args, **kwargs):
+ """Shortcut for::
+
+ bot.send_animation(Chat.id, *args, **kwargs)
+
+ Where Chat is the current instance.
+
+ Returns:
+ :class:`telegram.Message`: On success, instance representing the message posted.
+
+ """
+ return self.bot.send_animation(self.id, *args, **kwargs)
+
def send_sticker(self, *args, **kwargs):
"""Shortcut for::
diff --git a/telegram/ext/callbackqueryhandler.py b/telegram/ext/callbackqueryhandler.py
index 08b37b934..3c792e51f 100644
--- a/telegram/ext/callbackqueryhandler.py
+++ b/telegram/ext/callbackqueryhandler.py
@@ -50,7 +50,7 @@ class CallbackQueryHandler(Handler):
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
- can use to keep any data in will be sent to the :attr:`callback` function.. Related to
+ can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
diff --git a/telegram/ext/choseninlineresulthandler.py b/telegram/ext/choseninlineresulthandler.py
index 34efea357..0b02456d3 100644
--- a/telegram/ext/choseninlineresulthandler.py
+++ b/telegram/ext/choseninlineresulthandler.py
@@ -39,7 +39,7 @@ class ChosenInlineResultHandler(Handler):
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
- can use to keep any data in will be sent to the :attr:`callback` function.. Related to
+ can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
diff --git a/telegram/ext/commandhandler.py b/telegram/ext/commandhandler.py
index 82e8a40b6..f38f9a3aa 100644
--- a/telegram/ext/commandhandler.py
+++ b/telegram/ext/commandhandler.py
@@ -52,7 +52,7 @@ class CommandHandler(Handler):
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
- can use to keep any data in will be sent to the :attr:`callback` function.. Related to
+ can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
diff --git a/telegram/ext/dispatcher.py b/telegram/ext/dispatcher.py
index 7756770b7..0eeead9e9 100644
--- a/telegram/ext/dispatcher.py
+++ b/telegram/ext/dispatcher.py
@@ -143,7 +143,7 @@ class Dispatcher(object):
"""
if cls.__singleton is not None:
- return cls.__singleton()
+ return cls.__singleton() # pylint: disable=not-callable
else:
raise RuntimeError('{} not initialized or multiple instances exist'.format(
cls.__name__))
@@ -385,4 +385,4 @@ class Dispatcher(object):
else:
self.logger.exception(
- 'No error handlers are registered, logging exception...', exc_info=error)
+ 'No error handlers are registered, logging exception.', exc_info=error)
diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py
index 782cbd36d..784403e9e 100644
--- a/telegram/ext/filters.py
+++ b/telegram/ext/filters.py
@@ -302,6 +302,15 @@ class Filters(object):
document = _Document()
""":obj:`Filter`: Messages that contain :class:`telegram.Document`."""
+ class _Animation(BaseFilter):
+ name = 'Filters.animation'
+
+ def filter(self, message):
+ return bool(message.animation)
+
+ animation = _Animation()
+ """:obj:`Filter`: Messages that contain :class:`telegram.Animation`."""
+
class _Photo(BaseFilter):
name = 'Filters.photo'
@@ -677,6 +686,15 @@ class Filters(object):
successful_payment = _SuccessfulPayment()
""":obj:`Filter`: Messages that confirm a :class:`telegram.SuccessfulPayment`."""
+ class _PassportData(BaseFilter):
+ name = 'Filters.passport_data'
+
+ def filter(self, message):
+ return bool(message.passport_data)
+
+ passport_data = _PassportData()
+ """:obj:`Filter`: Messages that contain a :class:`telegram.PassportData`"""
+
class language(BaseFilter):
"""Filters messages to only allow those which are from users with a certain language code.
diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py
index d529b6128..5a3b6dc36 100644
--- a/telegram/ext/handler.py
+++ b/telegram/ext/handler.py
@@ -35,7 +35,7 @@ class Handler(object):
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
- can use to keep any data in will be sent to the :attr:`callback` function.. Related to
+ can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
diff --git a/telegram/ext/inlinequeryhandler.py b/telegram/ext/inlinequeryhandler.py
index c5a313d83..772cdf90f 100644
--- a/telegram/ext/inlinequeryhandler.py
+++ b/telegram/ext/inlinequeryhandler.py
@@ -50,7 +50,7 @@ class InlineQueryHandler(Handler):
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
- can use to keep any data in will be sent to the :attr:`callback` function.. Related to
+ can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py
index e9babc315..dadbee55c 100644
--- a/telegram/ext/messagehandler.py
+++ b/telegram/ext/messagehandler.py
@@ -50,7 +50,7 @@ class MessageHandler(Handler):
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
- can use to keep any data in will be sent to the :attr:`callback` function.. Related to
+ can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
diff --git a/telegram/ext/precheckoutqueryhandler.py b/telegram/ext/precheckoutqueryhandler.py
index 6f8b36054..e3bbe7c81 100644
--- a/telegram/ext/precheckoutqueryhandler.py
+++ b/telegram/ext/precheckoutqueryhandler.py
@@ -38,7 +38,7 @@ class PreCheckoutQueryHandler(Handler):
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
- can use to keep any data in will be sent to the :attr:`callback` function.. Related to
+ can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py
index 1e83a3eaa..934930dad 100644
--- a/telegram/ext/regexhandler.py
+++ b/telegram/ext/regexhandler.py
@@ -53,7 +53,7 @@ class RegexHandler(Handler):
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
- can use to keep any data in will be sent to the :attr:`callback` function.. Related to
+ can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
diff --git a/telegram/ext/shippingqueryhandler.py b/telegram/ext/shippingqueryhandler.py
index bc374d3be..663a3f725 100644
--- a/telegram/ext/shippingqueryhandler.py
+++ b/telegram/ext/shippingqueryhandler.py
@@ -38,7 +38,7 @@ class ShippingQueryHandler(Handler):
Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
- can use to keep any data in will be sent to the :attr:`callback` function.. Related to
+ can use to keep any data in will be sent to the :attr:`callback` function. Related to
either the user or the chat that the update was sent in. For each update from the same user
or in the same chat, it will be the same ``dict``.
diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py
index 78eae6ef3..cbf2e7495 100644
--- a/telegram/ext/updater.py
+++ b/telegram/ext/updater.py
@@ -66,6 +66,8 @@ class Updater(object):
bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance. If a pre-initialized
bot is used, it is the user's responsibility to create it using a `Request`
instance with a large enough connection pool.
+ private_key (:obj:`bytes`, optional): Private key for decryption of telegram passport data.
+ private_key_password (:obj:`bytes`, optional): Password for above private key.
user_sig_handler (:obj:`function`, optional): Takes ``signum, frame`` as positional
arguments. This will be called when a signal is received, defaults are (SIGINT,
SIGTERM, SIGABRT) setable with :attr:`idle`.
@@ -89,6 +91,8 @@ class Updater(object):
base_url=None,
workers=4,
bot=None,
+ private_key=None,
+ private_key_password=None,
user_sig_handler=None,
request_kwargs=None):
@@ -96,6 +100,8 @@ class Updater(object):
raise ValueError('`token` or `bot` must be passed')
if (token is not None) and (bot is not None):
raise ValueError('`token` and `bot` are mutually exclusive')
+ if (private_key is not None) and (bot is not None):
+ raise ValueError('`bot` and `private_key` are mutually exclusive')
self.logger = logging.getLogger(__name__)
@@ -119,7 +125,8 @@ class Updater(object):
if 'con_pool_size' not in request_kwargs:
request_kwargs['con_pool_size'] = con_pool_size
self._request = Request(**request_kwargs)
- self.bot = Bot(token, base_url, request=self._request)
+ self.bot = Bot(token, base_url, request=self._request, private_key=private_key,
+ private_key_password=private_key_password)
self.user_sig_handler = user_sig_handler
self.update_queue = Queue()
self.job_queue = JobQueue(self.bot)
diff --git a/telegram/games/animation.py b/telegram/files/animation.py
similarity index 79%
rename from telegram/games/animation.py
rename to telegram/files/animation.py
index e16246c8e..4ff659402 100644
--- a/telegram/games/animation.py
+++ b/telegram/files/animation.py
@@ -26,6 +26,9 @@ class Animation(TelegramObject):
Attributes:
file_id (:obj:`str`): Unique file identifier.
+ width (:obj:`int`): Video width as defined by sender.
+ height (:obj:`int`): Video height as defined by sender.
+ duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`): Optional. Animation thumbnail as defined
by sender.
file_name (:obj:`str`): Optional. Original animation filename as defined by sender.
@@ -34,6 +37,9 @@ class Animation(TelegramObject):
Args:
file_id (:obj:`str`): Unique file identifier.
+ width (:obj:`int`): Video width as defined by sender.
+ height (:obj:`int`): Video height as defined by sender.
+ duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`, optional): Animation thumbnail as defined by sender.
file_name (:obj:`str`, optional): Original animation filename as defined by sender.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
@@ -43,12 +49,19 @@ class Animation(TelegramObject):
def __init__(self,
file_id,
+ width,
+ height,
+ duration,
thumb=None,
file_name=None,
mime_type=None,
file_size=None,
**kwargs):
- self.file_id = file_id
+ # Required
+ self.file_id = str(file_id)
+ self.width = int(width)
+ self.height = int(height)
+ self.duration = duration
self.thumb = thumb
self.file_name = file_name
self.mime_type = mime_type
diff --git a/telegram/files/audio.py b/telegram/files/audio.py
index e960f2075..b743669e4 100644
--- a/telegram/files/audio.py
+++ b/telegram/files/audio.py
@@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Audio."""
-from telegram import TelegramObject
+from telegram import TelegramObject, PhotoSize
class Audio(TelegramObject):
@@ -32,6 +32,8 @@ class Audio(TelegramObject):
title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
file_size (:obj:`int`): Optional. File size.
+ thumb (:class:`telegram.PhotoSize`): Optional. Thumbnail of the album cover to
+ which the music file belongs
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
Args:
@@ -42,6 +44,8 @@ class Audio(TelegramObject):
title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
file_size (:obj:`int`, optional): File size.
+ thumb (:class:`telegram.PhotoSize`, optional): Thumbnail of the album cover to
+ which the music file belongs
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
@@ -54,6 +58,7 @@ class Audio(TelegramObject):
title=None,
mime_type=None,
file_size=None,
+ thumb=None,
bot=None,
**kwargs):
# Required
@@ -64,6 +69,7 @@ class Audio(TelegramObject):
self.title = title
self.mime_type = mime_type
self.file_size = file_size
+ self.thumb = thumb
self.bot = bot
self._id_attrs = (self.file_id,)
@@ -73,6 +79,8 @@ class Audio(TelegramObject):
if not data:
return None
+ data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
+
return cls(bot=bot, **data)
def get_file(self, timeout=None, **kwargs):
diff --git a/telegram/files/contact.py b/telegram/files/contact.py
index a70da4dfa..e3cd88c49 100644
--- a/telegram/files/contact.py
+++ b/telegram/files/contact.py
@@ -29,23 +29,27 @@ class Contact(TelegramObject):
first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`): Optional. Contact's last name.
user_id (:obj:`int`): Optional. Contact's user identifier in Telegram.
+ vcard (:obj:`str`): Optional. Additional data about the contact in the form of a vCard.
Args:
phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`, optional): Contact's last name.
user_id (:obj:`int`, optional): Contact's user identifier in Telegram.
+ vcard (:obj:`str`, optional): Additional data about the contact in the form of a vCard.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
- def __init__(self, phone_number, first_name, last_name=None, user_id=None, **kwargs):
+ def __init__(self, phone_number, first_name, last_name=None, user_id=None, vcard=None,
+ **kwargs):
# Required
self.phone_number = str(phone_number)
self.first_name = first_name
# Optionals
self.last_name = last_name
self.user_id = user_id
+ self.vcard = vcard
self._id_attrs = (self.phone_number,)
diff --git a/telegram/files/file.py b/telegram/files/file.py
index 1caec12b2..c3f7f7544 100644
--- a/telegram/files/file.py
+++ b/telegram/files/file.py
@@ -22,6 +22,7 @@ from os.path import basename
from future.backports.urllib import parse as urllib_parse
from telegram import TelegramObject
+from telegram.passport.credentials import decrypt
class File(TelegramObject):
@@ -45,6 +46,10 @@ class File(TelegramObject):
bot (:obj:`telegram.Bot`, optional): Bot to use with shortcut method.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
+ Note:
+ If you obtain an instance of this class from :attr:`telegram.PassportFile.get_file`,
+ then it will automatically be decrypted as it downloads when you call :attr:`download()`.
+
"""
def __init__(self, file_id, bot=None, file_size=None, file_path=None, **kwargs):
@@ -56,6 +61,7 @@ class File(TelegramObject):
self.file_path = file_path
self.bot = bot
+ self._credentials = None
self._id_attrs = (self.file_id,)
@@ -100,6 +106,8 @@ class File(TelegramObject):
if out:
buf = self.bot.request.retrieve(url)
+ if self._credentials:
+ buf = decrypt(self._credentials.secret, self._credentials.hash, buf, file=True)
out.write(buf)
return out
else:
@@ -108,7 +116,11 @@ class File(TelegramObject):
else:
filename = basename(self.file_path)
- self.bot.request.download(url, filename, timeout=timeout)
+ buf = self.bot.request.retrieve(url, timeout=timeout)
+ if self._credentials:
+ buf = decrypt(self._credentials.secret, self._credentials.hash, buf, file=True)
+ with open(filename, 'wb') as fobj:
+ fobj.write(buf)
return filename
def _get_encoded_url(self):
@@ -133,3 +145,6 @@ class File(TelegramObject):
buf.extend(self.bot.request.retrieve(self._get_encoded_url()))
return buf
+
+ def set_credentials(self, credentials):
+ self._credentials = credentials
diff --git a/telegram/files/inputfile.py b/telegram/files/inputfile.py
index d43e0fccf..a328ad8cf 100644
--- a/telegram/files/inputfile.py
+++ b/telegram/files/inputfile.py
@@ -19,132 +19,68 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram InputFile."""
-try:
- # python 3
- from email.generator import _make_boundary as choose_boundary
-except ImportError:
- # python 2
- from mimetools import choose_boundary
-
import imghdr
import mimetypes
import os
import sys
+from uuid import uuid4
from telegram import TelegramError
DEFAULT_MIME_TYPE = 'application/octet-stream'
-USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)'
-FILE_TYPES = ('audio', 'document', 'photo', 'sticker', 'video', 'voice', 'certificate',
- 'video_note', 'png_sticker')
class InputFile(object):
"""This object represents a Telegram InputFile.
Attributes:
- data (:obj:`dict`): Data containing an inputfile.
+ input_file_content (:obj:`bytes`): The binaray content of the file to send.
+ filename (:obj:`str`): Optional, Filename for the file to be sent.
+ attach (:obj:`str`): Optional, attach id for sending multiple files.
Args:
- data (:obj:`dict`): Data containing an inputfile.
+ obj (:obj:`File handler`): An open file descriptor.
+ filename (:obj:`str`, optional): Filename for this InputFile.
+ attach (:obj:`bool`, optional): Whether this should be send as one file or is part of a
+ collection of files.
Raises:
TelegramError
"""
- def __init__(self, data):
- self.data = data
- self.boundary = choose_boundary()
+ def __init__(self, obj, filename=None, attach=None):
+ self.filename = None
+ self.input_file_content = obj.read()
+ self.attach = 'attached' + uuid4().hex if attach else None
- for t in FILE_TYPES:
- if t in data:
- self.input_name = t
- self.input_file = data.pop(t)
- break
- else:
- raise TelegramError('Unknown inputfile type')
+ if filename:
+ self.filename = filename
+ elif (hasattr(obj, 'name') and
+ not isinstance(obj.name, int) and # py3
+ obj.name != ''): # py2
+ # on py2.7, pylint fails to understand this properly
+ # pylint: disable=E1101
+ self.filename = os.path.basename(obj.name)
- if hasattr(self.input_file, 'read'):
- self.filename = None
- self.input_file_content = self.input_file.read()
- if 'filename' in data:
- self.filename = self.data.pop('filename')
- elif (hasattr(self.input_file, 'name') and
- not isinstance(self.input_file.name, int) and # py3
- self.input_file.name != ''): # py2
- # on py2.7, pylint fails to understand this properly
- # pylint: disable=E1101
- self.filename = os.path.basename(self.input_file.name)
-
- try:
- self.mimetype = self.is_image(self.input_file_content)
- if not self.filename or '.' not in self.filename:
- self.filename = self.mimetype.replace('/', '.')
- except TelegramError:
- if self.filename:
- self.mimetype = mimetypes.guess_type(
- self.filename)[0] or DEFAULT_MIME_TYPE
- else:
- self.mimetype = DEFAULT_MIME_TYPE
+ try:
+ self.mimetype = self.is_image(self.input_file_content)
+ except TelegramError:
+ if self.filename:
+ self.mimetype = mimetypes.guess_type(
+ self.filename)[0] or DEFAULT_MIME_TYPE
+ else:
+ self.mimetype = DEFAULT_MIME_TYPE
+ if not self.filename or '.' not in self.filename:
+ self.filename = self.mimetype.replace('/', '.')
if sys.version_info < (3,):
if isinstance(self.filename, unicode): # flake8: noqa pylint: disable=E0602
self.filename = self.filename.encode('utf-8', 'replace')
@property
- def headers(self):
- """:obj:`dict`: Headers."""
-
- return {'User-agent': USER_AGENT, 'Content-type': self.content_type}
-
- @property
- def content_type(self):
- """:obj:`str`: Content type"""
- return 'multipart/form-data; boundary=%s' % self.boundary
-
- def to_form(self):
- """Transform the inputfile to multipart/form data.
-
- Returns:
- :obj:`str`
-
- """
- form = []
- form_boundary = '--' + self.boundary
-
- # Add data fields
- for name in iter(self.data):
- value = self.data[name]
- form.extend([
- form_boundary, 'Content-Disposition: form-data; name="%s"' % name, '', str(value)
- ])
-
- # Add input_file to upload
- form.extend([
- form_boundary, 'Content-Disposition: form-data; name="%s"; filename="%s"' %
- (self.input_name,
- self.filename), 'Content-Type: %s' % self.mimetype, '', self.input_file_content
- ])
-
- form.append('--' + self.boundary + '--')
- form.append('')
-
- return self._parse(form)
-
- @staticmethod
- def _parse(form):
- if sys.version_info > (3,):
- # on Python 3 form needs to be byte encoded
- encoded_form = []
- for item in form:
- try:
- encoded_form.append(item.encode())
- except AttributeError:
- encoded_form.append(item)
-
- return b'\r\n'.join(encoded_form)
- return '\r\n'.join(form)
+ def field_tuple(self):
+ return self.filename, self.input_file_content, self.mimetype
@staticmethod
def is_image(stream):
@@ -164,22 +100,9 @@ class InputFile(object):
raise TelegramError('Could not parse file content')
@staticmethod
- def is_inputfile(data):
- """Check if the request is a file request.
+ def is_file(obj):
+ return hasattr(obj, 'read')
- Args:
- data (Dict[:obj:`str`, :obj:`str`]): A dict of (str, str) key/value pairs.
-
- Returns:
- :obj:`bool`
-
- """
- if data:
- file_type = [i for i in iter(data) if i in FILE_TYPES]
-
- if file_type:
- file_content = data[file_type[0]]
-
- return hasattr(file_content, 'read')
-
- return False
+ def to_dict(self):
+ if self.attach:
+ return 'attach://' + self.attach
diff --git a/telegram/files/inputmedia.py b/telegram/files/inputmedia.py
index 114dcc9e9..b6398727d 100644
--- a/telegram/files/inputmedia.py
+++ b/telegram/files/inputmedia.py
@@ -18,14 +18,334 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""Base class for Telegram InputMedia Objects."""
-from telegram import TelegramObject
+from telegram import TelegramObject, InputFile, PhotoSize, Animation, Video, Audio, Document
class InputMedia(TelegramObject):
"""Base class for Telegram InputMedia Objects.
- See :class:`telegram.InputMediaPhoto` and :class:`telegram.InputMediaVideo` for
- detailed use.
+ See :class:`telegram.InputMediaAnimation`, :class:`telegram.InputMediaAudio`,
+ :class:`telegram.InputMediaDocument`, :class:`telegram.InputMediaPhoto` and
+ :class:`telegram.InputMediaVideo` for detailed use.
"""
pass
+
+
+class InputMediaAnimation(InputMedia):
+ """Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent.
+
+ Attributes:
+ type (:obj:`str`): ``animation``.
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
+ servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
+ Lastly you can pass an existing :class:`telegram.Animation` object to send.
+ thumb (`filelike object`): Optional. Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+ caption (:obj:`str`): Optional. Caption of the animation to be sent, 0-200 characters.
+ parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+ width (:obj:`int`): Optional. Animation width.
+ height (:obj:`int`): Optional. Animation height.
+ duration (:obj:`int`): Optional. Animation duration.
+
+
+ Args:
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
+ servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
+ Lastly you can pass an existing :class:`telegram.Animation` object to send.
+ thumb (`filelike object`, optional): Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+ caption (:obj:`str`, optional): Caption of the animation to be sent, 0-200 characters.
+ parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+ width (:obj:`int`, optional): Animation width.
+ height (:obj:`int`, optional): Animation height.
+ duration (:obj:`int`, optional): Animation duration.
+
+ Note:
+ When using a :class:`telegram.Animation` for the :attr:`media` attribute. It will take the
+ width, height and duration from that video, unless otherwise specified with the optional
+ arguments.
+ """
+
+ def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None,
+ duration=None):
+ self.type = 'animation'
+
+ if isinstance(media, Animation):
+ self.media = media.file_id
+ self.width = media.width
+ self.height = media.height
+ self.duration = media.duration
+ elif InputFile.is_file(media):
+ self.media = InputFile(media, attach=True)
+ else:
+ self.media = media
+
+ if thumb:
+ self.thumb = thumb
+ if InputFile.is_file(self.thumb):
+ self.thumb = InputFile(self.thumb, attach=True)
+
+ if caption:
+ self.caption = caption
+ if parse_mode:
+ self.parse_mode = parse_mode
+ if width:
+ self.width = width
+ if height:
+ self.height = height
+ if duration:
+ self.duration = duration
+
+
+class InputMediaPhoto(InputMedia):
+ """Represents a photo to be sent.
+
+ Attributes:
+ type (:obj:`str`): ``photo``.
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the
+ Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the
+ Internet. Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
+ caption (:obj:`str`): Optional. Caption of the photo to be sent, 0-200 characters.
+ parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+
+ Args:
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the
+ Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the
+ Internet. Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
+ caption (:obj:`str`, optional ): Caption of the photo to be sent, 0-200 characters.
+ parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+ """
+
+ def __init__(self, media, caption=None, parse_mode=None):
+ self.type = 'photo'
+
+ if isinstance(media, PhotoSize):
+ self.media = media.file_id
+ elif InputFile.is_file(media):
+ self.media = InputFile(media, attach=True)
+ else:
+ self.media = media
+
+ if caption:
+ self.caption = caption
+ if parse_mode:
+ self.parse_mode = parse_mode
+
+
+class InputMediaVideo(InputMedia):
+ """Represents a video to be sent.
+
+ Attributes:
+ type (:obj:`str`): ``video``.
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
+ servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
+ Lastly you can pass an existing :class:`telegram.Video` object to send.
+ caption (:obj:`str`): Optional. Caption of the video to be sent, 0-200 characters.
+ parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+ width (:obj:`int`): Optional. Video width.
+ height (:obj:`int`): Optional. Video height.
+ duration (:obj:`int`): Optional. Video duration.
+ supports_streaming (:obj:`bool`): Optional. Pass True, if the uploaded video is suitable
+ for streaming.
+ thumb (`filelike object`): Optional. Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+
+ Args:
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
+ servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
+ Lastly you can pass an existing :class:`telegram.Video` object to send.
+ caption (:obj:`str`, optional): Caption of the video to be sent, 0-200 characters.
+ parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+ width (:obj:`int`, optional): Video width.
+ height (:obj:`int`, optional): Video height.
+ duration (:obj:`int`, optional): Video duration.
+ supports_streaming (:obj:`bool`, optional): Pass True, if the uploaded video is suitable
+ for streaming.
+ thumb (`filelike object`, optional): Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+
+ Note:
+ When using a :class:`telegram.Video` for the :attr:`media` attribute. It will take the
+ width, height and duration from that video, unless otherwise specified with the optional
+ arguments.
+ """
+
+ def __init__(self, media, caption=None, width=None, height=None, duration=None,
+ supports_streaming=None, parse_mode=None, thumb=None):
+ self.type = 'video'
+
+ if isinstance(media, Video):
+ self.media = media.file_id
+ self.width = media.width
+ self.height = media.height
+ self.duration = media.duration
+ elif InputFile.is_file(media):
+ self.media = InputFile(media, attach=True)
+ else:
+ self.media = media
+
+ if thumb:
+ self.thumb = thumb
+ if InputFile.is_file(self.thumb):
+ self.thumb = InputFile(self.thumb, attach=True)
+
+ if caption:
+ self.caption = caption
+ if parse_mode:
+ self.parse_mode = parse_mode
+ if width:
+ self.width = width
+ if height:
+ self.height = height
+ if duration:
+ self.duration = duration
+ if supports_streaming:
+ self.supports_streaming = supports_streaming
+
+
+class InputMediaAudio(InputMedia):
+ """Represents an audio file to be treated as music to be sent.
+
+ Attributes:
+ type (:obj:`str`): ``audio``.
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
+ servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
+ Lastly you can pass an existing :class:`telegram.Audio` object to send.
+ caption (:obj:`str`): Optional. Caption of the audio to be sent, 0-200 characters.
+ parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+ duration (:obj:`int`): Duration of the audio in seconds.
+ performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio
+ tags.
+ title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags.
+ thumb (`filelike object`): Optional. Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+
+ Args:
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
+ servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
+ Lastly you can pass an existing :class:`telegram.Document` object to send.
+ caption (:obj:`str`, optional): Caption of the audio to be sent, 0-200 characters.
+ parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+ duration (:obj:`int`): Duration of the audio in seconds as defined by sender.
+ performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio
+ tags.
+ title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags.
+ thumb (`filelike object`, optional): Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+
+ Note:
+ When using a :class:`telegram.Audio` for the :attr:`media` attribute. It will take the
+ duration, performer and title from that video, unless otherwise specified with the
+ optional arguments.
+ """
+
+ def __init__(self, media, thumb=None, caption=None, parse_mode=None,
+ duration=None, performer=None, title=None):
+ self.type = 'audio'
+
+ if isinstance(media, Audio):
+ self.media = media.file_id
+ self.duration = media.duration
+ self.performer = media.performer
+ self.title = media.title
+ elif InputFile.is_file(media):
+ self.media = InputFile(media, attach=True)
+ else:
+ self.media = media
+
+ if thumb:
+ self.thumb = thumb
+ if InputFile.is_file(self.thumb):
+ self.thumb = InputFile(self.thumb, attach=True)
+
+ if caption:
+ self.caption = caption
+ if parse_mode:
+ self.parse_mode = parse_mode
+ if duration:
+ self.duration = duration
+ if performer:
+ self.performer = performer
+ if title:
+ self.title = title
+
+
+class InputMediaDocument(InputMedia):
+ """Represents a general file to be sent.
+
+ Attributes:
+ type (:obj:`str`): ``document``.
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
+ servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
+ Lastly you can pass an existing :class:`telegram.Document` object to send.
+ caption (:obj:`str`): Optional. Caption of the document to be sent, 0-200 characters.
+ parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+ thumb (`filelike object`): Optional. Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+
+ Args:
+ media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
+ servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
+ Lastly you can pass an existing :class:`telegram.Document` object to send.
+ caption (:obj:`str`, optional): Caption of the document to be sent, 0-200 characters.
+ parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
+ in :class:`telegram.ParseMode` for the available modes.
+ thumb (`filelike object`, optional): Thumbnail of the
+ file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
+ A thumbnail's width and height should not exceed 90. Ignored if the file is not
+ is passed as a string or file_id.
+ """
+
+ def __init__(self, media, thumb=None, caption=None, parse_mode=None):
+ self.type = 'document'
+
+ if isinstance(media, Document):
+ self.media = media.file_id
+ elif InputFile.is_file(media):
+ self.media = InputFile(media, attach=True)
+ else:
+ self.media = media
+
+ if thumb:
+ self.thumb = thumb
+ if InputFile.is_file(self.thumb):
+ self.thumb = InputFile(self.thumb, attach=True)
+
+ if caption:
+ self.caption = caption
+ if parse_mode:
+ self.parse_mode = parse_mode
diff --git a/telegram/files/inputmediaphoto.py b/telegram/files/inputmediaphoto.py
deleted file mode 100644
index 9e3f4d985..000000000
--- a/telegram/files/inputmediaphoto.py
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env python
-#
-# A library that provides a Python interface to the Telegram Bot API
-# Copyright (C) 2015-2018
-# Leandro Toledo de Souza
-#
-# 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 InputMediaPhoto."""
-from telegram import InputMedia, PhotoSize
-
-
-class InputMediaPhoto(InputMedia):
- """Represents a photo to be sent.
-
- Attributes:
- type (:obj:`str`): ``photo``.
- media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the
- Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the
- Internet. Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
- caption (:obj:`str`): Optional. Caption of the photo to be sent, 0-200 characters.
- parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
- in :class:`telegram.ParseMode` for the available modes.
-
- Args:
- media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the
- Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the
- Internet. Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
- caption (:obj:`str`, optional ): Caption of the photo to be sent, 0-200 characters.
- parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
- in :class:`telegram.ParseMode` for the available modes.
-
- Note:
- At the moment using a new file is not yet supported.
- """
-
- # TODO: Make InputMediaPhoto, InputMediaVideo and send_media_group work with new files
-
- def __init__(self, media, caption=None, parse_mode=None):
- self.type = 'photo'
-
- if isinstance(media, PhotoSize):
- self.media = media.file_id
- elif hasattr(media, 'read'):
- raise ValueError(
- 'Sending files is not supported (yet). Use file_id, url or PhotoSize')
- else:
- self.media = media
-
- if caption:
- self.caption = caption
- if parse_mode:
- self.parse_mode = parse_mode
diff --git a/telegram/files/inputmediavideo.py b/telegram/files/inputmediavideo.py
deleted file mode 100644
index 977c7ea39..000000000
--- a/telegram/files/inputmediavideo.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python
-#
-# A library that provides a Python interface to the Telegram Bot API
-# Copyright (C) 2015-2018
-# Leandro Toledo de Souza
-#
-# 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 InputMediaPhoto."""
-from telegram import InputMedia, Video
-
-
-class InputMediaVideo(InputMedia):
- """Represents a video to be sent.
-
- Attributes:
- type (:obj:`str`): ``video``.
- media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
- servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
- Lastly you can pass an existing :class:`telegram.Video` object to send.
- caption (:obj:`str`): Optional. Caption of the video to be sent, 0-200 characters.
- parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
- in :class:`telegram.ParseMode` for the available modes.
- width (:obj:`int`): Optional. Video width.
- height (:obj:`int`): Optional. Video height.
- duration (:obj:`int`): Optional. Video duration.
- supports_streaming (:obj:`bool`): Optional. Pass True, if the uploaded video is suitable
- for streaming.
-
- Args:
- media (:obj:`str`): File to send. Pass a file_id to send a file that exists on the Telegram
- servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet.
- Lastly you can pass an existing :class:`telegram.Video` object to send.
- caption (:obj:`str`, optional): Caption of the video to be sent, 0-200 characters.
- parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
- in :class:`telegram.ParseMode` for the available modes.
- width (:obj:`int`, optional): Video width.
- height (:obj:`int`, optional): Video height.
- duration (:obj:`int`, optional): Video duration.
- supports_streaming (:obj:`bool`, optional): Pass True, if the uploaded video is suitable
- for streaming.
-
- Note:
- When using a :class:`telegram.Video` for the :attr:`media` attribute. It will take the
- width, height and duration from that video, unless otherwise specified with the optional
- arguments.
- At the moment using a new file is not yet supported.
- """
-
- # TODO: Make InputMediaPhoto, InputMediaVideo and send_media_group work with new files
-
- def __init__(self, media, caption=None, width=None, height=None, duration=None,
- supports_streaming=None, parse_mode=None):
- self.type = 'video'
-
- if isinstance(media, Video):
- self.media = media.file_id
- self.width = media.width
- self.height = media.height
- self.duration = media.duration
- elif hasattr(media, 'read'):
- raise ValueError('Sending files is not supported (yet). Use file_id, url or Video')
- else:
- self.media = media
-
- if caption:
- self.caption = caption
- if parse_mode:
- self.parse_mode = parse_mode
- if width:
- self.width = width
- if height:
- self.height = height
- if duration:
- self.duration = duration
- if supports_streaming:
- self.supports_streaming = supports_streaming
diff --git a/telegram/files/venue.py b/telegram/files/venue.py
index 68a110a6f..27779eaa8 100644
--- a/telegram/files/venue.py
+++ b/telegram/files/venue.py
@@ -29,23 +29,29 @@ class Venue(TelegramObject):
title (:obj:`str`): Name of the venue.
address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue.
+ foursquare_type (:obj:`str`): Optional. Foursquare type of the venue. (For example,
+ "arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".)
Args:
location (:class:`telegram.Location`): Venue location.
title (:obj:`str`): Name of the venue.
address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue.
+ foursquare_type (:obj:`str`, optional): Foursquare type of the venue. (For example,
+ "arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".)
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
- def __init__(self, location, title, address, foursquare_id=None, **kwargs):
+ def __init__(self, location, title, address, foursquare_id=None, foursquare_type=None,
+ **kwargs):
# Required
self.location = location
self.title = title
self.address = address
# Optionals
self.foursquare_id = foursquare_id
+ self.foursquare_type = foursquare_type
self._id_attrs = (self.location, self.title)
diff --git a/telegram/inline/inlinequeryresultaudio.py b/telegram/inline/inlinequeryresultaudio.py
index 10b1cb9c2..0bd2c3ce5 100644
--- a/telegram/inline/inlinequeryresultaudio.py
+++ b/telegram/inline/inlinequeryresultaudio.py
@@ -36,7 +36,7 @@ class InlineQueryResultAudio(InlineQueryResult):
audio_duration (:obj:`str`): Optional. Performer.
caption (:obj:`str`): Optional. Audio duration in seconds.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -51,7 +51,7 @@ class InlineQueryResultAudio(InlineQueryResult):
audio_duration (:obj:`str`, optional): Performer.
caption (:obj:`str`, optional): Audio duration in seconds.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultcachedaudio.py b/telegram/inline/inlinequeryresultcachedaudio.py
index 405274957..221616fbd 100644
--- a/telegram/inline/inlinequeryresultcachedaudio.py
+++ b/telegram/inline/inlinequeryresultcachedaudio.py
@@ -33,7 +33,7 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
audio_file_id (:obj:`str`): A valid file identifier for the audio file.
caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -45,7 +45,7 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
audio_file_id (:obj:`str`): A valid file identifier for the audio file.
caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultcachedgif.py b/telegram/inline/inlinequeryresultcachedgif.py
index 107031432..dd01b33c1 100644
--- a/telegram/inline/inlinequeryresultcachedgif.py
+++ b/telegram/inline/inlinequeryresultcachedgif.py
@@ -35,7 +35,7 @@ class InlineQueryResultCachedGif(InlineQueryResult):
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -48,7 +48,7 @@ class InlineQueryResultCachedGif(InlineQueryResult):
title (:obj:`str`, optional): Title for the result.caption (:obj:`str`, optional):
caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultcachedmpeg4gif.py b/telegram/inline/inlinequeryresultcachedmpeg4gif.py
index 027241a24..41f099e9b 100644
--- a/telegram/inline/inlinequeryresultcachedmpeg4gif.py
+++ b/telegram/inline/inlinequeryresultcachedmpeg4gif.py
@@ -35,7 +35,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -48,7 +48,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultcachedphoto.py b/telegram/inline/inlinequeryresultcachedphoto.py
index 49742e4a0..89b8dad11 100644
--- a/telegram/inline/inlinequeryresultcachedphoto.py
+++ b/telegram/inline/inlinequeryresultcachedphoto.py
@@ -36,7 +36,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
description (:obj:`str`): Optional. Short description of the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -50,7 +50,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
description (:obj:`str`, optional): Short description of the result.
caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultcachedvideo.py b/telegram/inline/inlinequeryresultcachedvideo.py
index 51006d9c5..75cd67577 100644
--- a/telegram/inline/inlinequeryresultcachedvideo.py
+++ b/telegram/inline/inlinequeryresultcachedvideo.py
@@ -36,7 +36,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
description (:obj:`str`): Optional. Short description of the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -50,7 +50,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
description (:obj:`str`, optional): Short description of the result.
caption (:obj:`str`, optional): Caption, 0-200 characters.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultcachedvoice.py b/telegram/inline/inlinequeryresultcachedvoice.py
index 9247a9e39..503325c49 100644
--- a/telegram/inline/inlinequeryresultcachedvoice.py
+++ b/telegram/inline/inlinequeryresultcachedvoice.py
@@ -34,7 +34,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
title (:obj:`str`): Voice message title.
caption (:obj:`str`): Optional. Caption, 0-200 characters.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -47,7 +47,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
title (:obj:`str`): Voice message title.
caption (:obj:`str`, optional): Caption, 0-200 characters.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultcontact.py b/telegram/inline/inlinequeryresultcontact.py
index 9c6e46158..2963cfc37 100644
--- a/telegram/inline/inlinequeryresultcontact.py
+++ b/telegram/inline/inlinequeryresultcontact.py
@@ -33,6 +33,8 @@ class InlineQueryResultContact(InlineQueryResult):
phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`): Optional. Contact's last name.
+ vcard (:obj:`str`): Optional. Additional data about the contact in the form of a vCard,
+ 0-2048 bytes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
@@ -46,6 +48,8 @@ class InlineQueryResultContact(InlineQueryResult):
phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`, optional): Contact's last name.
+ vcard (:obj:`str`, optional): Additional data about the contact in the form of a vCard,
+ 0-2048 bytes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
@@ -67,6 +71,7 @@ class InlineQueryResultContact(InlineQueryResult):
thumb_url=None,
thumb_width=None,
thumb_height=None,
+ vcard=None,
**kwargs):
# Required
super(InlineQueryResultContact, self).__init__('contact', id)
@@ -76,6 +81,8 @@ class InlineQueryResultContact(InlineQueryResult):
# Optionals
if last_name:
self.last_name = last_name
+ if vcard:
+ self.vcard = vcard
if reply_markup:
self.reply_markup = reply_markup
if input_message_content:
diff --git a/telegram/inline/inlinequeryresultdocument.py b/telegram/inline/inlinequeryresultdocument.py
index 5dadcd63a..a4584a963 100644
--- a/telegram/inline/inlinequeryresultdocument.py
+++ b/telegram/inline/inlinequeryresultdocument.py
@@ -34,7 +34,7 @@ class InlineQueryResultDocument(InlineQueryResult):
title (:obj:`str`): Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
document_url (:obj:`str`): A valid URL for the file.
mime_type (:obj:`str`): Mime type of the content of the file, either "application/pdf"
@@ -53,7 +53,7 @@ class InlineQueryResultDocument(InlineQueryResult):
title (:obj:`str`): Title for the result.
caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
document_url (:obj:`str`): A valid URL for the file.
mime_type (:obj:`str`): Mime type of the content of the file, either "application/pdf"
diff --git a/telegram/inline/inlinequeryresultgif.py b/telegram/inline/inlinequeryresultgif.py
index 410afda4f..d8b62c6e5 100644
--- a/telegram/inline/inlinequeryresultgif.py
+++ b/telegram/inline/inlinequeryresultgif.py
@@ -38,7 +38,7 @@ class InlineQueryResultGif(InlineQueryResult):
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -55,7 +55,7 @@ class InlineQueryResultGif(InlineQueryResult):
title (:obj:`str`, optional): Title for the result.caption (:obj:`str`, optional):
caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultmpeg4gif.py b/telegram/inline/inlinequeryresultmpeg4gif.py
index e00073812..3459d775b 100644
--- a/telegram/inline/inlinequeryresultmpeg4gif.py
+++ b/telegram/inline/inlinequeryresultmpeg4gif.py
@@ -39,7 +39,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -56,7 +56,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultphoto.py b/telegram/inline/inlinequeryresultphoto.py
index 0b474e839..efaf95fde 100644
--- a/telegram/inline/inlinequeryresultphoto.py
+++ b/telegram/inline/inlinequeryresultphoto.py
@@ -39,7 +39,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
description (:obj:`str`): Optional. Short description of the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
@@ -57,7 +57,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
description (:obj:`str`, optional): Short description of the result.
caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
diff --git a/telegram/inline/inlinequeryresultvenue.py b/telegram/inline/inlinequeryresultvenue.py
index 93e577648..f64fcc205 100644
--- a/telegram/inline/inlinequeryresultvenue.py
+++ b/telegram/inline/inlinequeryresultvenue.py
@@ -35,6 +35,9 @@ class InlineQueryResultVenue(InlineQueryResult):
title (:obj:`str`): Title of the venue.
address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue if known.
+ foursquare_type (:obj:`str`): Optional. Foursquare type of the venue, if known.
+ (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or
+ "food/icecream".)
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
@@ -50,6 +53,9 @@ class InlineQueryResultVenue(InlineQueryResult):
title (:obj:`str`): Title of the venue.
address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue if known.
+ foursquare_type (:obj:`str`, optional): Foursquare type of the venue, if known.
+ (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or
+ "food/icecream".)
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
@@ -68,6 +74,7 @@ class InlineQueryResultVenue(InlineQueryResult):
title,
address,
foursquare_id=None,
+ foursquare_type=None,
reply_markup=None,
input_message_content=None,
thumb_url=None,
@@ -85,6 +92,8 @@ class InlineQueryResultVenue(InlineQueryResult):
# Optional
if foursquare_id:
self.foursquare_id = foursquare_id
+ if foursquare_type:
+ self.foursquare_type = foursquare_type
if reply_markup:
self.reply_markup = reply_markup
if input_message_content:
diff --git a/telegram/inline/inlinequeryresultvideo.py b/telegram/inline/inlinequeryresultvideo.py
index a55161d22..a21c52509 100644
--- a/telegram/inline/inlinequeryresultvideo.py
+++ b/telegram/inline/inlinequeryresultvideo.py
@@ -37,7 +37,7 @@ class InlineQueryResultVideo(InlineQueryResult):
title (:obj:`str`): Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
video_width (:obj:`int`): Optional. Video width.
video_height (:obj:`int`): Optional. Video height.
@@ -56,7 +56,7 @@ class InlineQueryResultVideo(InlineQueryResult):
title (:obj:`str`): Title for the result.
caption (:obj:`str`, optional): Caption, 0-200 characters.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
- bold, italic, fixed-width text or inline URLs in the media caption.. See the constants
+ bold, italic, fixed-width text or inline URLs in the media caption. See the constants
in :class:`telegram.ParseMode` for the available modes.
video_width (:obj:`int`, optional): Video width.
video_height (:obj:`int`, optional): Video height.
diff --git a/telegram/inline/inputcontactmessagecontent.py b/telegram/inline/inputcontactmessagecontent.py
index cec5e6bde..c655b0fd3 100644
--- a/telegram/inline/inputcontactmessagecontent.py
+++ b/telegram/inline/inputcontactmessagecontent.py
@@ -28,18 +28,23 @@ class InputContactMessageContent(InputMessageContent):
phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`): Optional. Contact's last name.
+ vcard (:obj:`str`): Optional. Additional data about the contact in the form of a vCard,
+ 0-2048 bytes.
Args:
phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`, optional): Contact's last name.
+ vcard (:obj:`str`, optional): Additional data about the contact in the form of a vCard,
+ 0-2048 bytes.
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
- def __init__(self, phone_number, first_name, last_name=None, **kwargs):
+ def __init__(self, phone_number, first_name, last_name=None, vcard=None, **kwargs):
# Required
self.phone_number = phone_number
self.first_name = first_name
# Optionals
self.last_name = last_name
+ self.vcard = vcard
diff --git a/telegram/inline/inputvenuemessagecontent.py b/telegram/inline/inputvenuemessagecontent.py
index a8ea20c0b..d0f1fdc5b 100644
--- a/telegram/inline/inputvenuemessagecontent.py
+++ b/telegram/inline/inputvenuemessagecontent.py
@@ -30,6 +30,9 @@ class InputVenueMessageContent(InputMessageContent):
title (:obj:`str`): Name of the venue.
address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue, if known.
+ foursquare_type (:obj:`str`): Optional. Foursquare type of the venue, if known.
+ (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or
+ "food/icecream".)
Args:
latitude (:obj:`float`): Latitude of the location in degrees.
@@ -37,11 +40,15 @@ class InputVenueMessageContent(InputMessageContent):
title (:obj:`str`): Name of the venue.
address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue, if known.
+ foursquare_type (:obj:`str`, optional): Foursquare type of the venue, if known.
+ (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or
+ "food/icecream".)
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
"""
- def __init__(self, latitude, longitude, title, address, foursquare_id=None, **kwargs):
+ def __init__(self, latitude, longitude, title, address, foursquare_id=None,
+ foursquare_type=None, **kwargs):
# Required
self.latitude = latitude
self.longitude = longitude
@@ -49,3 +56,4 @@ class InputVenueMessageContent(InputMessageContent):
self.address = address
# Optionals
self.foursquare_id = foursquare_id
+ self.foursquare_type = foursquare_type
diff --git a/telegram/message.py b/telegram/message.py
index 14d579932..09b07d41c 100644
--- a/telegram/message.py
+++ b/telegram/message.py
@@ -21,9 +21,9 @@
import sys
from html import escape
-from telegram import (Audio, Contact, Document, Chat, Location, PhotoSize, Sticker, TelegramObject,
- User, Video, Voice, Venue, MessageEntity, Game, Invoice, SuccessfulPayment,
- VideoNote)
+from telegram import (Animation, Audio, Contact, Document, Chat, Location, PhotoSize, Sticker,
+ TelegramObject, User, Video, Voice, Venue, MessageEntity, Game, Invoice,
+ SuccessfulPayment, VideoNote, PassportData)
from telegram import ParseMode
from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp
@@ -62,6 +62,9 @@ class Message(TelegramObject):
to use properly.
audio (:class:`telegram.Audio`): Optional. Information about the file.
document (:class:`telegram.Document`): Optional. Information about the file.
+ animation (:class:`telegram.Animation`) Optional. Information about the file.
+ For backward compatibility, when this field is set, the document field will also be
+ set.
game (:class:`telegram.Game`): Optional. Information about the game.
photo (List[:class:`telegram.PhotoSize`]): Optional. Available sizes of the photo.
sticker (:class:`telegram.Sticker`): Optional. Information about the sticker.
@@ -97,6 +100,7 @@ class Message(TelegramObject):
forwarded from channels.
author_signature (:obj:`str`): Optional. Signature of the post author for messages
in channels.
+ passport_data (:class:`telegram.PassportData`): Optional. Telegram Passport data
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
Args:
@@ -134,6 +138,9 @@ class Message(TelegramObject):
about the file.
document (:class:`telegram.Document`, optional): Message is a general file, information
about the file.
+ animation (:class:`telegram.Animation`, optional): Message is an animation, information
+ about the animation. For backward compatibility, when this field is set, the document
+ field will also be set.
game (:class:`telegram.Game`, optional): Message is a game, information about the game.
photo (List[:class:`telegram.PhotoSize`], optional): Message is a photo, available
sizes of the photo.
@@ -192,17 +199,18 @@ class Message(TelegramObject):
forwarded from channels.
author_signature (:obj:`str`, optional): Signature of the post author for messages
in channels.
+ passport_data (:class:`telegram.PassportData`, optional): Telegram Passport data
"""
_effective_attachment = _UNDEFINED
- ATTACHMENT_TYPES = ['audio', 'game', 'document', 'photo', 'sticker', 'video', 'voice',
- 'video_note', 'contact', 'location', 'venue', 'invoice',
+ ATTACHMENT_TYPES = ['audio', 'game', 'animation', 'document', 'photo', 'sticker', 'video',
+ 'voice', 'video_note', 'contact', 'location', 'venue', 'invoice',
'successful_payment']
MESSAGE_TYPES = ['text', 'new_chat_members', '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'] + ATTACHMENT_TYPES
+ 'pinned_message', 'passport_data'] + ATTACHMENT_TYPES
def __init__(self,
message_id,
@@ -247,6 +255,8 @@ class Message(TelegramObject):
author_signature=None,
media_group_id=None,
connected_website=None,
+ animation=None,
+ passport_data=None,
bot=None,
**kwargs):
# Required
@@ -293,6 +303,8 @@ class Message(TelegramObject):
self.forward_signature = forward_signature
self.author_signature = author_signature
self.media_group_id = media_group_id
+ self.animation = animation
+ self.passport_data = passport_data
self.bot = bot
@@ -330,6 +342,7 @@ class Message(TelegramObject):
data['edit_date'] = from_timestamp(data.get('edit_date'))
data['audio'] = Audio.de_json(data.get('audio'), bot)
data['document'] = Document.de_json(data.get('document'), bot)
+ data['animation'] = Animation.de_json(data.get('animation'), bot)
data['game'] = Game.de_json(data.get('game'), bot)
data['photo'] = PhotoSize.de_list(data.get('photo'), bot)
data['sticker'] = Sticker.de_json(data.get('sticker'), bot)
@@ -345,6 +358,7 @@ class Message(TelegramObject):
data['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
data['invoice'] = Invoice.de_json(data.get('invoice'), bot)
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot)
+ data['passport_data'] = PassportData.de_json(data.get('passport_data'), bot)
return cls(bot=bot, **data)
@@ -354,6 +368,7 @@ class Message(TelegramObject):
:class:`telegram.Audio`
or :class:`telegram.Contact`
or :class:`telegram.Document`
+ or :class:`telegram.Animation`
or :class:`telegram.Game`
or :class:`telegram.Invoice`
or :class:`telegram.Location`
@@ -551,6 +566,23 @@ class Message(TelegramObject):
self._quote(kwargs)
return self.bot.send_document(self.chat_id, *args, **kwargs)
+ def reply_animation(self, *args, **kwargs):
+ """Shortcut for::
+
+ bot.send_animation(update.message.chat_id, *args, **kwargs)
+
+ Keyword Args:
+ quote (:obj:`bool`, optional): If set to ``True``, the photo 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_animation(self.chat_id, *args, **kwargs)
+
def reply_sticker(self, *args, **kwargs):
"""Shortcut for::
@@ -699,7 +731,7 @@ class Message(TelegramObject):
Note:
You can only edit messages that the bot sent itself,
therefore this method can only be used on the
- return value of the ``bot.send_*`` family of methods..
+ return value of the ``bot.send_*`` family of methods.
Returns:
:class:`telegram.Message`: On success, instance representing the edited message.
@@ -728,6 +760,27 @@ class Message(TelegramObject):
return self.bot.edit_message_caption(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs)
+ def edit_media(self, media, *args, **kwargs):
+ """Shortcut for::
+
+ bot.edit_message_media(chat_id=message.chat_id,
+ message_id=message.message_id,
+ *args,
+ **kwargs)
+
+ Note:
+ You can only edit messages that the bot sent itself,
+ therefore this method can only be used on the
+ return value of the ``bot.send_*`` family of methods.
+
+ Returns:
+ :class:`telegram.Message`: On success, instance representing the edited
+ message.
+
+ """
+ return self.bot.edit_message_media(
+ chat_id=self.chat_id, message_id=self.message_id, media=media, *args, **kwargs)
+
def edit_reply_markup(self, *args, **kwargs):
"""Shortcut for::
diff --git a/telegram/messageentity.py b/telegram/messageentity.py
index 0c837300c..9617ee5af 100644
--- a/telegram/messageentity.py
+++ b/telegram/messageentity.py
@@ -81,6 +81,10 @@ class MessageEntity(TelegramObject):
""":obj:`str`: 'mention'"""
HASHTAG = 'hashtag'
""":obj:`str`: 'hashtag'"""
+ CASHTAG = 'cashtag'
+ """:obj:`str`: 'cashtag'"""
+ PHONE_NUMBER = 'phone_number'
+ """:obj:`str`: 'phone_number'"""
BOT_COMMAND = 'bot_command'
""":obj:`str`: 'bot_command'"""
URL = 'url'
@@ -100,6 +104,7 @@ class MessageEntity(TelegramObject):
TEXT_MENTION = 'text_mention'
""":obj:`str`: 'text_mention'"""
ALL_TYPES = [
- MENTION, HASHTAG, BOT_COMMAND, URL, EMAIL, BOLD, ITALIC, CODE, PRE, TEXT_LINK, TEXT_MENTION
+ MENTION, HASHTAG, CASHTAG, PHONE_NUMBER, BOT_COMMAND, URL,
+ EMAIL, BOLD, ITALIC, CODE, PRE, TEXT_LINK, TEXT_MENTION
]
"""List[:obj:`str`]: List of all the types."""
diff --git a/telegram/passport/__init__.py b/telegram/passport/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/telegram/passport/credentials.py b/telegram/passport/credentials.py
new file mode 100644
index 000000000..35c236d8f
--- /dev/null
+++ b/telegram/passport/credentials.py
@@ -0,0 +1,446 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 binascii
+try:
+ import ujson as json
+except ImportError:
+ import json
+from base64 import b64decode
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric.padding import OAEP, MGF1
+from cryptography.hazmat.primitives.ciphers import Cipher
+from cryptography.hazmat.primitives.ciphers.algorithms import AES
+from cryptography.hazmat.primitives.ciphers.modes import CBC
+from cryptography.hazmat.primitives.hashes import SHA512, SHA256, Hash, SHA1
+from future.utils import bord
+
+from telegram import TelegramObject, TelegramError
+
+
+class TelegramDecryptionError(TelegramError):
+ """
+ Something went wrong with decryption.
+ """
+
+ def __init__(self, message):
+ super(TelegramDecryptionError, self).__init__("TelegramDecryptionError: "
+ "{}".format(message))
+
+
+def decrypt(secret, hash, data, file=False):
+ """
+ Decrypt per telegram docs at https://core.telegram.org/passport.
+
+ Args:
+ secret (:obj:`str` or :obj:`bytes`): The encryption secret, either as bytes or as a
+ base64 encoded string.
+ hash (:obj:`str` or :obj:`bytes`): The hash, either as bytes or as a
+ base64 encoded string.
+ data (:obj:`str` or :obj:`bytes`): The data to decrypt, either as bytes or as a
+ base64 encoded string.
+ file (:obj:`bool`): Force data to be treated as raw data, instead of trying to
+ b64decode it.
+
+ Raises:
+ :class:`TelegramDecryptionError`: Given hash does not match hash of decrypted data.
+
+ Returns:
+ :obj:`bytes`: The decrypted data as bytes.
+
+ """
+ # First make sure that if secret, hash, or data was base64 encoded, to decode it into bytes
+ try:
+ secret = b64decode(secret)
+ except (binascii.Error, TypeError):
+ pass
+ try:
+ hash = b64decode(hash)
+ except (binascii.Error, TypeError):
+ pass
+ if not file:
+ try:
+ data = b64decode(data)
+ except (binascii.Error, TypeError):
+ pass
+ # Make a SHA512 hash of secret + update
+ digest = Hash(SHA512(), backend=default_backend())
+ digest.update(secret + hash)
+ secret_hash_hash = digest.finalize()
+ # First 32 chars is our key, next 16 is the initialisation vector
+ key, iv = secret_hash_hash[:32], secret_hash_hash[32:32 + 16]
+ # Init a AES-CBC cipher and decrypt the data
+ cipher = Cipher(AES(key), CBC(iv), backend=default_backend())
+ decryptor = cipher.decryptor()
+ data = decryptor.update(data) + decryptor.finalize()
+ # Calculate SHA256 hash of the decrypted data
+ digest = Hash(SHA256(), backend=default_backend())
+ digest.update(data)
+ data_hash = digest.finalize()
+ # If the newly calculated hash did not match the one telegram gave us
+ if data_hash != hash:
+ # Raise a error that is caught inside telegram.PassportData and transformed into a warning
+ raise TelegramDecryptionError("Hashes are not equal! {} != {}".format(data_hash, hash))
+ # Return data without padding
+ return data[bord(data[0]):]
+
+
+def decrypt_json(secret, hash, data):
+ """Decrypts data using secret and hash and then decodes utf-8 string and loads json"""
+ return json.loads(decrypt(secret, hash, data).decode('utf-8'))
+
+
+class EncryptedCredentials(TelegramObject):
+ """Contains data required for decrypting and authenticating EncryptedPassportElement. See the
+ Telegram Passport Documentation for a complete description of the data decryption and
+ authentication processes.
+
+ Attributes:
+ data (:class:`telegram.Credentials` or :obj:`str`): Decrypted data with unique user's
+ payload, data hashes and secrets used for EncryptedPassportElement decryption and
+ authentication or base64 encrypted data.
+ hash (:obj:`str`): Base64-encoded data hash for data authentication.
+ secret (:obj:`str`): Decrypted or encrypted secret used for decryption.
+
+ Args:
+ data (:class:`telegram.Credentials` or :obj:`str`): Decrypted data with unique user's
+ payload, data hashes and secrets used for EncryptedPassportElement decryption and
+ authentication or base64 encrypted data.
+ hash (:obj:`str`): Base64-encoded data hash for data authentication.
+ secret (:obj:`str`): Decrypted or encrypted secret used for decryption.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ Note:
+ This object is decrypted only when originating from
+ :obj:`telegram.PassportData.decrypted_credentials`.
+
+ """
+
+ def __init__(self, data, hash, secret, bot=None, **kwargs):
+ # Required
+ self.data = data
+ self.hash = hash
+ self.secret = secret
+
+ self._id_attrs = (self.data, self.hash, self.secret)
+
+ self.bot = bot
+ self._decrypted_secret = None
+ self._decrypted_data = None
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ data = super(EncryptedCredentials, cls).de_json(data, bot)
+
+ return cls(bot=bot, **data)
+
+ @property
+ def decrypted_secret(self):
+ """
+ :obj:`str`: Lazily decrypt and return secret.
+
+ Raises:
+ telegram.TelegramDecryptionError: Decryption failed. Usually due to bad
+ private/public key but can also suggest malformed/tampered data.
+ """
+ if self._decrypted_secret is None:
+ # Try decrypting according to step 1 at
+ # https://core.telegram.org/passport#decrypting-data
+ # We make sure to base64 decode the secret first.
+ # Telegram says to use OAEP padding so we do that. The Mask Generation Function
+ # is the default for OAEP, the algorithm is the default for PHP which is what
+ # Telegram's backend servers run.
+ try:
+ self._decrypted_secret = self.bot.private_key.decrypt(b64decode(self.secret), OAEP(
+ mgf=MGF1(algorithm=SHA1()),
+ algorithm=SHA1(),
+ label=None
+ ))
+ except ValueError as e:
+ # If decryption fails raise exception
+ raise TelegramDecryptionError(e)
+ return self._decrypted_secret
+
+ @property
+ def decrypted_data(self):
+ """
+ :class:`telegram.Credentials`: Lazily decrypt and return credentials data. This object
+ also contains the user specified payload as
+ `decrypted_data.payload`.
+
+ Raises:
+ telegram.TelegramDecryptionError: Decryption failed. Usually due to bad
+ private/public key but can also suggest malformed/tampered data.
+ """
+ if self._decrypted_data is None:
+ self._decrypted_data = Credentials.de_json(decrypt_json(self.decrypted_secret,
+ self.hash,
+ self.data),
+ self.bot)
+ return self._decrypted_data
+
+
+class Credentials(TelegramObject):
+ """
+ Attributes:
+ secure_data (:class:`telegram.SecureData`): Credentials for encrypted data
+ payload (:obj:`str`): Bot-specified payload
+ """
+
+ def __init__(self, secure_data, payload, bot=None, **kwargs):
+ # Required
+ self.secure_data = secure_data
+ self.payload = payload
+
+ self.bot = bot
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ data['secure_data'] = SecureData.de_json(data.get('secure_data'), bot=bot)
+
+ return cls(bot=bot, **data)
+
+
+class SecureData(TelegramObject):
+ """
+ This object represents the credentials that were used to decrypt the encrypted data.
+ All fields are optional and depend on fields that were requested.
+
+ Attributes:
+ personal_details (:class:`telegram.SecureValue`, optional): Credentials for encrypted
+ personal details.
+ passport (:class:`telegram.SecureValue`, optional): Credentials for encrypted passport.
+ internal_passport (:class:`telegram.SecureValue`, optional): Credentials for encrypted
+ internal passport.
+ driver_license (:class:`telegram.SecureValue`, optional): Credentials for encrypted
+ driver license.
+ identity_card (:class:`telegram.SecureValue`, optional): Credentials for encrypted ID card
+ address (:class:`telegram.SecureValue`, optional): Credentials for encrypted
+ residential address.
+ utility_bill (:class:`telegram.SecureValue`, optional): Credentials for encrypted
+ utility bill.
+ bank_statement (:class:`telegram.SecureValue`, optional): Credentials for encrypted
+ bank statement.
+ rental_agreement (:class:`telegram.SecureValue`, optional): Credentials for encrypted
+ rental agreement.
+ passport_registration (:class:`telegram.SecureValue`, optional): Credentials for encrypted
+ registration from internal passport.
+ temporary_registration (:class:`telegram.SecureValue`, optional): Credentials for encrypted
+ temporary registration.
+ """
+
+ def __init__(self,
+ personal_details=None,
+ passport=None,
+ internal_passport=None,
+ driver_license=None,
+ identity_card=None,
+ address=None,
+ utility_bill=None,
+ bank_statement=None,
+ rental_agreement=None,
+ passport_registration=None,
+ temporary_registration=None,
+ bot=None,
+ **kwargs):
+ # Optionals
+ self.temporary_registration = temporary_registration
+ self.passport_registration = passport_registration
+ self.rental_agreement = rental_agreement
+ self.bank_statement = bank_statement
+ self.utility_bill = utility_bill
+ self.address = address
+ self.identity_card = identity_card
+ self.driver_license = driver_license
+ self.internal_passport = internal_passport
+ self.passport = passport
+ self.personal_details = personal_details
+
+ self.bot = bot
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ data['temporary_registration'] = SecureValue.de_json(data.get('temporary_registration'),
+ bot=bot)
+ data['passport_registration'] = SecureValue.de_json(data.get('passport_registration'),
+ bot=bot)
+ data['rental_agreement'] = SecureValue.de_json(data.get('rental_agreement'), bot=bot)
+ data['bank_statement'] = SecureValue.de_json(data.get('bank_statement'), bot=bot)
+ data['utility_bill'] = SecureValue.de_json(data.get('utility_bill'), bot=bot)
+ data['address'] = SecureValue.de_json(data.get('address'), bot=bot)
+ data['identity_card'] = SecureValue.de_json(data.get('identity_card'), bot=bot)
+ data['driver_license'] = SecureValue.de_json(data.get('driver_license'), bot=bot)
+ data['internal_passport'] = SecureValue.de_json(data.get('internal_passport'), bot=bot)
+ data['passport'] = SecureValue.de_json(data.get('passport'), bot=bot)
+ data['personal_details'] = SecureValue.de_json(data.get('personal_details'), bot=bot)
+
+ return cls(bot=bot, **data)
+
+
+class SecureValue(TelegramObject):
+ """
+ This object represents the credentials that were used to decrypt the encrypted value.
+ All fields are optional and depend on the type of field.
+
+ Attributes:
+ data (:class:`telegram.DataCredentials`, optional): Credentials for encrypted Telegram
+ Passport data. Available for "personal_details", "passport", "driver_license",
+ "identity_card", "identity_passport" and "address" types.
+ front_side (:class:`telegram.FileCredentials`, optional): Credentials for encrypted
+ document's front side. Available for "passport", "driver_license", "identity_card"
+ and "internal_passport".
+ reverse_side (:class:`telegram.FileCredentials`, optional): Credentials for encrypted
+ document's reverse side. Available for "driver_license" and "identity_card".
+ selfie (:class:`telegram.FileCredentials`, optional): Credentials for encrypted selfie
+ of the user with a document. Can be available for "passport", "driver_license",
+ "identity_card" and "internal_passport".
+ files (:class:`telegram.Array of FileCredentials`, optional): Credentials for encrypted
+ files. Available for "utility_bill", "bank_statement", "rental_agreement",
+ "passport_registration" and "temporary_registration" types.
+
+ """
+
+ def __init__(self,
+ data=None,
+ front_side=None,
+ reverse_side=None,
+ selfie=None,
+ files=None,
+ bot=None,
+ **kwargs):
+ self.data = data
+ self.front_side = front_side
+ self.reverse_side = reverse_side
+ self.selfie = selfie
+ self.files = files
+
+ self.bot = bot
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ data['data'] = DataCredentials.de_json(data.get('data'), bot=bot)
+ data['front_side'] = FileCredentials.de_json(data.get('front_side'), bot=bot)
+ data['reverse_side'] = FileCredentials.de_json(data.get('reverse_side'), bot=bot)
+ data['selfie'] = FileCredentials.de_json(data.get('selfie'), bot=bot)
+ data['files'] = FileCredentials.de_list(data.get('files'), bot=bot)
+
+ return cls(bot=bot, **data)
+
+ def to_dict(self):
+ data = super(SecureValue, self).to_dict()
+
+ data['files'] = [p.to_dict() for p in self.files]
+
+ return data
+
+
+class _CredentialsBase(TelegramObject):
+ """Base class for DataCredentials and FileCredentials."""
+
+ def __init__(self, hash, secret, bot=None, **kwargs):
+ self.hash = hash
+ self.secret = secret
+
+ # Aliases just be be sure
+ self.file_hash = self.hash
+ self.data_hash = self.hash
+
+ self.bot = bot
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ return cls(bot=bot, **data)
+
+ @classmethod
+ def de_list(cls, data, bot):
+ if not data:
+ return []
+
+ credentials = list()
+ for c in data:
+ credentials.append(cls.de_json(c, bot=bot))
+
+ return credentials
+
+
+class DataCredentials(_CredentialsBase):
+ """
+ These credentials can be used to decrypt encrypted data from the data field in
+ EncryptedPassportData.
+
+ Args:
+ data_hash (:obj:`str`): Checksum of encrypted data
+ secret (:obj:`str`): Secret of encrypted data
+
+ Attributes:
+ hash (:obj:`str`): Checksum of encrypted data
+ secret (:obj:`str`): Secret of encrypted data
+ """
+
+ def __init__(self, data_hash, secret, **kwargs):
+ super(DataCredentials, self).__init__(data_hash, secret, **kwargs)
+
+ def to_dict(self):
+ data = super(DataCredentials, self).to_dict()
+
+ del data['file_hash']
+ del data['hash']
+
+ return data
+
+
+class FileCredentials(_CredentialsBase):
+ """
+ These credentials can be used to decrypt encrypted files from the front_side,
+ reverse_side, selfie and files fields in EncryptedPassportData.
+
+ Args:
+ file_hash (:obj:`str`): Checksum of encrypted file
+ secret (:obj:`str`): Secret of encrypted file
+
+ Attributes:
+ hash (:obj:`str`): Checksum of encrypted file
+ secret (:obj:`str`): Secret of encrypted file
+ """
+
+ def __init__(self, file_hash, secret, **kwargs):
+ super(FileCredentials, self).__init__(file_hash, secret, **kwargs)
+
+ def to_dict(self):
+ data = super(FileCredentials, self).to_dict()
+
+ del data['data_hash']
+ del data['hash']
+
+ return data
diff --git a/telegram/passport/data.py b/telegram/passport/data.py
new file mode 100644
index 000000000..dd7d6d3b4
--- /dev/null
+++ b/telegram/passport/data.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2017
+# Leandro Toledo de Souza
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser Public License for more details.
+#
+# You should have received a copy of the GNU Lesser Public License
+# along with this program. If not, see [http://www.gnu.org/licenses/].
+from telegram import TelegramObject
+
+
+class PersonalDetails(TelegramObject):
+ """
+ This object represents personal details.
+
+ Attributes:
+ first_name (:obj:`str`): First Name.
+ last_name (:obj:`str`): Last Name.
+ birth_date (:obj:`str`): Date of birth in DD.MM.YYYY format.
+ gender (:obj:`str`): Gender, male or female.
+ country_code (:obj:`str`): Citizenship (ISO 3166-1 alpha-2 country code).
+ residence_country_code (:obj:`str`): Country of residence (ISO 3166-1 alpha-2 country
+ code).
+ """
+
+ def __init__(self, first_name, last_name, birth_date, gender, country_code,
+ residence_country_code, bot=None, **kwargs):
+ # Required
+ self.first_name = first_name
+ self.last_name = last_name
+ self.birth_date = birth_date
+ self.gender = gender
+ self.country_code = country_code
+ self.residence_country_code = residence_country_code
+
+ self.bot = bot
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ return cls(bot=bot, **data)
+
+
+class ResidentialAddress(TelegramObject):
+ """
+ This object represents a residential address.
+
+ Attributes:
+ street_line1 (:obj:`str`): First line for the address.
+ street_line2 (:obj:`str`): Optional. Second line for the address.
+ city (:obj:`str`): City.
+ state (:obj:`str`): Optional. State.
+ country_code (:obj:`str`): ISO 3166-1 alpha-2 country code.
+ post_code (:obj:`str`): Address post code.
+ """
+
+ def __init__(self, street_line1, street_line2, city, state, country_code,
+ post_code, bot=None, **kwargs):
+ # Required
+ self.street_line1 = street_line1
+ self.street_line2 = street_line2
+ self.city = city
+ self.state = state
+ self.country_code = country_code
+ self.post_code = post_code
+
+ self.bot = bot
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ return cls(bot=bot, **data)
+
+
+class IdDocumentData(TelegramObject):
+ """
+ This object represents the data of an identity document.
+
+ Attributes:
+ document_no (:obj:`str`): Document number.
+ expiry_date (:obj:`str`): Optional. Date of expiry, in DD.MM.YYYY format.
+ """
+
+ def __init__(self, document_no, expiry_date, bot=None, **kwargs):
+ self.document_no = document_no
+ self.expiry_date = expiry_date
+
+ self.bot = bot
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ return cls(bot=bot, **data)
diff --git a/telegram/passport/encryptedpassportelement.py b/telegram/passport/encryptedpassportelement.py
new file mode 100644
index 000000000..7402fc43c
--- /dev/null
+++ b/telegram/passport/encryptedpassportelement.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+# flake8: noqa: E501
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 EncryptedPassportElement."""
+
+from telegram import (TelegramObject, PassportFile, PersonalDetails, IdDocumentData,
+ ResidentialAddress)
+from telegram.passport.credentials import decrypt_json
+
+
+class EncryptedPassportElement(TelegramObject):
+ """
+ Contains information about documents or other Telegram Passport elements shared with the bot
+ by the user. The data has been automatically decrypted by python-telegram-bot.
+
+ Attributes:
+ type (:obj:`str`): Element type. One of "personal_details", "passport", "driver_license",
+ "identity_card", "internal_passport", "address", "utility_bill", "bank_statement",
+ "rental_agreement", "passport_registration", "temporary_registration", "phone_number",
+ "email".
+ data (:class:`telegram.PersonalDetails` or :class:`telegram.IdDocument` or :class:`telegram.ResidentialAddress` or :obj:`str`):
+ Optional. Decrypted or encrypted data, available for "personal_details", "passport",
+ "driver_license", "identity_card", "identity_passport" and "address" types.
+ phone_number (:obj:`str`): Optional. User's verified phone number, available only for
+ "phone_number" type.
+ email (:obj:`str`): Optional. User's verified email address, available only for "email"
+ type.
+ files (List[:class:`telegram.PassportFile`]): Optional. Array of encrypted/decrypted files
+ with documents provided by the user, available for "utility_bill", "bank_statement",
+ "rental_agreement", "passport_registration" and "temporary_registration" types.
+ front_side (:class:`PassportFile`): Optional. Encrypted/decrypted file with the front side
+ of the document, provided by the user. Available for "passport", "driver_license",
+ "identity_card" and "internal_passport".
+ reverse_side (:class:`PassportFile`): Optional. Encrypted/decrypted file with the reverse
+ side of the document, provided by the user. Available for "driver_license" and
+ "identity_card".
+ selfie (:class:`PassportFile`): Optional. Encrypted/decrypted file with the selfie of the
+ user holding a document, provided by the user; available for "passport",
+ "driver_license", "identity_card" and "internal_passport".
+ bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
+
+ Args:
+ type (:obj:`str`): Element type. One of "personal_details", "passport", "driver_license",
+ "identity_card", "internal_passport", "address", "utility_bill", "bank_statement",
+ "rental_agreement", "passport_registration", "temporary_registration", "phone_number",
+ "email".
+ data (:class:`telegram.PersonalDetails` or :class:`telegram.IdDocument` or :class:`telegram.ResidentialAddress` or :obj:`str`, optional):
+ Decrypted or encrypted data, available for "personal_details", "passport",
+ "driver_license", "identity_card", "identity_passport" and "address" types.
+ phone_number (:obj:`str`, optional): User's verified phone number, available only for
+ "phone_number" type.
+ email (:obj:`str`, optional): User's verified email address, available only for "email"
+ type.
+ files (List[:class:`telegram.PassportFile`], optional): Array of encrypted/decrypted files
+ with documents provided by the user, available for "utility_bill", "bank_statement",
+ "rental_agreement", "passport_registration" and "temporary_registration" types.
+ front_side (:class:`PassportFile`, optional): Encrypted/decrypted file with the front side
+ of the document, provided by the user. Available for "passport", "driver_license",
+ "identity_card" and "internal_passport".
+ reverse_side (:class:`PassportFile`, optional): Encrypted/decrypted file with the reverse
+ side of the document, provided by the user. Available for "driver_license" and
+ "identity_card".
+ selfie (:class:`PassportFile`, optional): Encrypted/decrypted file with the selfie of the
+ user holding a document, provided by the user; available for "passport",
+ "driver_license", "identity_card" and "internal_passport".
+ bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ Note:
+ This object is decrypted only when originating from
+ :obj:`telegram.PassportData.decrypted_data`.
+ """
+
+ def __init__(self,
+ type,
+ data=None,
+ phone_number=None,
+ email=None,
+ files=None,
+ front_side=None,
+ reverse_side=None,
+ selfie=None,
+ bot=None,
+ credentials=None,
+ **kwargs):
+ # Required
+ self.type = type
+ # Optionals
+ self.data = data
+ self.phone_number = phone_number
+ self.email = email
+ self.files = files
+ self.front_side = front_side
+ self.reverse_side = reverse_side
+ self.selfie = selfie
+
+ self._id_attrs = (self.type, self.data, self.phone_number, self.email, self.files,
+ self.front_side, self.reverse_side, self.selfie)
+
+ self.bot = bot
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ data = super(EncryptedPassportElement, cls).de_json(data, bot)
+
+ data['files'] = PassportFile.de_list(data.get('files'), bot) or None
+ data['front_side'] = PassportFile.de_json(data.get('front_side'), bot)
+ data['reverse_side'] = PassportFile.de_json(data.get('reverse_side'), bot)
+ data['selfie'] = PassportFile.de_json(data.get('selfie'), bot)
+
+ return cls(bot=bot, **data)
+
+ @classmethod
+ def de_json_decrypted(cls, data, bot, credentials):
+ if not data:
+ return None
+
+ data = super(EncryptedPassportElement, cls).de_json(data, bot)
+
+ if data['type'] not in ('phone_number', 'email'):
+ secure_data = getattr(credentials.secure_data, data['type'])
+
+ if secure_data.data is not None:
+ # If not already decrypted
+ if not isinstance(data['data'], dict):
+ data['data'] = decrypt_json(secure_data.data.secret,
+ secure_data.data.hash,
+ data['data'])
+ if data['type'] == 'personal_details':
+ data['data'] = PersonalDetails.de_json(data['data'], bot=bot)
+ elif data['type'] in ('passport', 'internal_passport',
+ 'driver_license', 'identity_card'):
+ data['data'] = IdDocumentData.de_json(data['data'], bot=bot)
+ elif data['type'] == 'address':
+ data['data'] = ResidentialAddress.de_json(data['data'], bot=bot)
+
+ data['files'] = PassportFile.de_list_decrypted(data.get('files'), bot,
+ secure_data) or None
+ data['front_side'] = PassportFile.de_json_decrypted(data.get('front_side'), bot,
+ secure_data.front_side)
+ data['reverse_side'] = PassportFile.de_json_decrypted(data.get('reverse_side'), bot,
+ secure_data.reverse_side)
+ data['selfie'] = PassportFile.de_json_decrypted(data.get('selfie'), bot,
+ secure_data.selfie)
+
+ return cls(bot=bot, **data)
+
+ @classmethod
+ def de_list(cls, data, bot):
+ if not data:
+ return []
+
+ encrypted_passport_elements = list()
+ for element in data:
+ encrypted_passport_elements.append(cls.de_json(element, bot))
+
+ return encrypted_passport_elements
+
+ def to_dict(self):
+ data = super(EncryptedPassportElement, self).to_dict()
+
+ if self.files:
+ data['files'] = [p.to_dict() for p in self.files]
+
+ return data
diff --git a/telegram/passport/passportdata.py b/telegram/passport/passportdata.py
new file mode 100644
index 000000000..67101da80
--- /dev/null
+++ b/telegram/passport/passportdata.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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/].
+"""Contains information about Telegram Passport data shared with the bot by the user."""
+
+from telegram import EncryptedCredentials, EncryptedPassportElement, TelegramObject
+
+
+class PassportData(TelegramObject):
+ """Contains information about Telegram Passport data shared with the bot by the user.
+
+ Attributes:
+ data (List[:class:`telegram.EncryptedPassportElement`]): Array with encrypted information
+ about documents and other Telegram Passport elements that was shared with the bot.
+ credentials (:class:`telegram.EncryptedCredentials`): Encrypted credentials.
+ bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
+
+ Args:
+ data (List[:class:`telegram.EncryptedPassportElement`]): Array with encrypted information
+ about documents and other Telegram Passport elements that was shared with the bot.
+ credentials (:obj:`str`): Encrypted credentials.
+ bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ Note:
+ To be able to decrypt this object, you must pass your private_key to either
+ :class:`telegram.Updater` or :class:`telegram.Bot`. Decrypted data is then found in
+ :attr:`decrypted_data` and the payload can be found in :attr:`decrypted_credentials`'s
+ attribute :attr:`telegram.Credentials.payload`.
+
+ """
+
+ def __init__(self, data, credentials, bot=None, **kwargs):
+ self.data = data
+ self.credentials = credentials
+
+ self.bot = bot
+ self._decrypted_data = None
+ self._id_attrs = tuple([x.type for x in data] + [credentials.hash])
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ data = super(PassportData, cls).de_json(data, bot)
+
+ data['data'] = EncryptedPassportElement.de_list(data.get('data'), bot)
+ data['credentials'] = EncryptedCredentials.de_json(data.get('credentials'), bot)
+
+ return cls(bot=bot, **data)
+
+ def to_dict(self):
+ data = super(PassportData, self).to_dict()
+
+ data['data'] = [e.to_dict() for e in self.data]
+
+ return data
+
+ @property
+ def decrypted_data(self):
+ """
+ List[:class:`telegram.EncryptedPassportElement`]: Lazily decrypt and return information
+ about documents and other Telegram Passport elements which were shared with the bot.
+
+ Raises:
+ telegram.TelegramDecryptionError: Decryption failed. Usually due to bad
+ private/public key but can also suggest malformed/tampered data.
+ """
+ if self._decrypted_data is None:
+ self._decrypted_data = [
+ EncryptedPassportElement.de_json_decrypted(element.to_dict(),
+ self.bot,
+ self.decrypted_credentials)
+ for element in self.data
+ ]
+ return self._decrypted_data
+
+ @property
+ def decrypted_credentials(self):
+ """
+ :class:`telegram.Credentials`: Lazily decrypt and return credentials that were used
+ to decrypt the data. This object also contains the user specified payload as
+ `decrypted_data.payload`.
+
+ Raises:
+ telegram.TelegramDecryptionError: Decryption failed. Usually due to bad
+ private/public key but can also suggest malformed/tampered data.
+ """
+ return self.credentials.decrypted_data
diff --git a/telegram/passport/passportelementerrors.py b/telegram/passport/passportelementerrors.py
new file mode 100644
index 000000000..7ee63a998
--- /dev/null
+++ b/telegram/passport/passportelementerrors.py
@@ -0,0 +1,252 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser Public License for more details.
+#
+# You should have received a copy of the GNU Lesser Public License
+# along with this program. If not, see [http://www.gnu.org/licenses/].
+"""This module contains the classes that represent Telegram PassportElementError."""
+
+from telegram import TelegramObject
+
+
+class PassportElementError(TelegramObject):
+ """Baseclass for the PassportElementError* classes.
+
+ Attributes:
+ source (:obj:`str`): Error source.
+ type (:obj:`str`): The section of the user's Telegram Passport which has the error.
+ message (:obj:`str`): Error message
+
+ Args:
+ source (:obj:`str`): Error source.
+ type (:obj:`str`): The section of the user's Telegram Passport which has the error.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ """
+
+ def __init__(self, source, type, message, **kwargs):
+ # Required
+ self.source = str(source)
+ self.type = str(type)
+ self.message = str(message)
+
+ self._id_attrs = (self.source, self.type)
+
+
+class PassportElementErrorDataField(PassportElementError):
+ """
+ Represents an issue in one of the data fields that was provided by the user. The error is
+ considered resolved when the field's value changes.
+
+ Attributes:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the error, one of
+ "personal_details", "passport", "driver_license", "identity_card", "internal_passport",
+ "address".
+ field_name (:obj:`str`): Name of the data field which has the error.
+ data_hash (:obj:`str`): Base64-encoded data hash.
+ message (:obj:`str`): Error message.
+
+ Args:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the error, one of
+ "personal_details", "passport", "driver_license", "identity_card", "internal_passport",
+ "address".
+ field_name (:obj:`str`): Name of the data field which has the error.
+ data_hash (:obj:`str`): Base64-encoded data hash.
+ message (:obj:`str`): Error message.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ """
+
+ def __init__(self,
+ type,
+ field_name,
+ data_hash,
+ message,
+ **kwargs):
+ # Required
+ super(PassportElementErrorDataField, self).__init__('data', type, message)
+ self.field_name = field_name
+ self.data_hash = data_hash
+
+ self._id_attrs = (self.source, self.type, self.field_name, self.data_hash, self.message)
+
+
+class PassportElementErrorFile(PassportElementError):
+ """
+ Represents an issue with a document scan. The error is considered resolved when the file with
+ the document scan changes.
+
+ Attributes:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "utility_bill", "bank_statement", "rental_agreement", "passport_registration",
+ "temporary_registration".
+ file_hash (:obj:`str`): Base64-encoded file hash.
+ message (:obj:`str`): Error message.
+
+ Args:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "utility_bill", "bank_statement", "rental_agreement", "passport_registration",
+ "temporary_registration".
+ file_hash (:obj:`str`): Base64-encoded file hash.
+ message (:obj:`str`): Error message.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ """
+
+ def __init__(self,
+ type,
+ file_hash,
+ message,
+ **kwargs):
+ # Required
+ super(PassportElementErrorFile, self).__init__('file', type, message)
+ self.file_hash = file_hash
+
+ self._id_attrs = (self.source, self.type, self.file_hash, self.message)
+
+
+class PassportElementErrorFiles(PassportElementError):
+ """
+ Represents an issue with a list of scans. The error is considered resolved when the file with
+ the document scan changes.
+
+ Attributes:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "utility_bill", "bank_statement", "rental_agreement", "passport_registration",
+ "temporary_registration".
+ file_hash (:obj:`str`): Base64-encoded file hash.
+ message (:obj:`str`): Error message.
+
+ Args:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "utility_bill", "bank_statement", "rental_agreement", "passport_registration",
+ "temporary_registration".
+ file_hashes (List[:obj:`str`]): List of base64-encoded file hashes.
+ message (:obj:`str`): Error message.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ """
+
+ def __init__(self,
+ type,
+ file_hashes,
+ message,
+ **kwargs):
+ # Required
+ super(PassportElementErrorFiles, self).__init__('files', type, message)
+ self.file_hashes = file_hashes
+
+ self._id_attrs = ((self.source, self.type, self.message) +
+ tuple([file_hash for file_hash in file_hashes]))
+
+
+class PassportElementErrorFrontSide(PassportElementError):
+ """
+ Represents an issue with the front side of a document. The error is considered resolved when
+ the file with the front side of the document changes.
+
+ Attributes:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "passport", "driver_license", "identity_card", "internal_passport".
+ file_hash (:obj:`str`): Base64-encoded hash of the file with the front side of the
+ document.
+ message (:obj:`str`): Error message.
+
+ Args:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "passport", "driver_license", "identity_card", "internal_passport".
+ file_hash (:obj:`str`): Base64-encoded hash of the file with the front side of the
+ document.
+ message (:obj:`str`): Error message.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ """
+
+ def __init__(self,
+ type,
+ file_hash,
+ message,
+ **kwargs):
+ # Required
+ super(PassportElementErrorFrontSide, self).__init__('front_side', type, message)
+ self.file_hash = file_hash
+
+ self._id_attrs = (self.source, self.type, self.file_hash, self.message)
+
+
+class PassportElementErrorReverseSide(PassportElementError):
+ """
+ Represents an issue with the front side of a document. The error is considered resolved when
+ the file with the reverse side of the document changes.
+
+ Attributes:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "passport", "driver_license", "identity_card", "internal_passport".
+ file_hash (:obj:`str`): Base64-encoded hash of the file with the reverse side of the
+ document.
+ message (:obj:`str`): Error message.
+
+ Args:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "driver_license", "identity_card".
+ file_hash (:obj:`str`): Base64-encoded hash of the file with the reverse side of the
+ document.
+ message (:obj:`str`): Error message.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ """
+
+ def __init__(self,
+ type,
+ file_hash,
+ message,
+ **kwargs):
+ # Required
+ super(PassportElementErrorReverseSide, self).__init__('reverse_side', type, message)
+ self.file_hash = file_hash
+
+ self._id_attrs = (self.source, self.type, self.file_hash, self.message)
+
+
+class PassportElementErrorSelfie(PassportElementError):
+ """
+ Represents an issue with the selfie with a document. The error is considered resolved when
+ the file with the selfie changes.
+
+ Attributes:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "passport", "driver_license", "identity_card", "internal_passport".
+ file_hash (:obj:`str`): Base64-encoded hash of the file with the selfie.
+ message (:obj:`str`): Error message.
+
+ Args:
+ type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
+ "passport", "driver_license", "identity_card", "internal_passport".
+ file_hash (:obj:`str`): Base64-encoded hash of the file with the selfie.
+ message (:obj:`str`): Error message.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ """
+
+ def __init__(self,
+ type,
+ file_hash,
+ message,
+ **kwargs):
+ # Required
+ super(PassportElementErrorSelfie, self).__init__('selfie', type, message)
+ self.file_hash = file_hash
+
+ self._id_attrs = (self.source, self.type, self.file_hash, self.message)
diff --git a/telegram/passport/passportfile.py b/telegram/passport/passportfile.py
new file mode 100644
index 000000000..2d5fe4a48
--- /dev/null
+++ b/telegram/passport/passportfile.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 Encrypted PassportFile."""
+
+from telegram import TelegramObject
+
+
+class PassportFile(TelegramObject):
+ """
+ This object represents a file uploaded to Telegram Passport. Currently all Telegram Passport
+ files are in JPEG format when decrypted and don't exceed 10MB.
+
+ Attributes:
+ file_id (:obj:`str`): Unique identifier for this file.
+ file_size (:obj:`int`): File size.
+ file_date (:obj:`int`): Unix time when the file was uploaded.
+ bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
+
+ Args:
+ file_id (:obj:`str`): Unique identifier for this file.
+ file_size (:obj:`int`): File size.
+ file_date (:obj:`int`): Unix time when the file was uploaded.
+ bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
+ **kwargs (:obj:`dict`): Arbitrary keyword arguments.
+
+ """
+
+ def __init__(self, file_id, file_date, file_size=None, bot=None, credentials=None, **kwargs):
+ # Required
+ self.file_id = file_id
+ self.file_size = file_size
+ self.file_date = file_date
+ # Optionals
+ self.bot = bot
+ self._credentials = credentials
+
+ self._id_attrs = (self.file_id,)
+
+ @classmethod
+ def de_json(cls, data, bot):
+ if not data:
+ return None
+
+ data = super(PassportFile, cls).de_json(data, bot)
+
+ return cls(bot=bot, **data)
+
+ @classmethod
+ def de_json_decrypted(cls, data, bot, credentials):
+ if not data:
+ return None
+
+ data = super(PassportFile, cls).de_json(data, bot)
+
+ data['credentials'] = credentials
+
+ return cls(bot=bot, **data)
+
+ @classmethod
+ def de_list(cls, data, bot):
+ if not data:
+ return []
+
+ return [cls.de_json(passport_file, bot) for passport_file in data]
+
+ @classmethod
+ def de_list_decrypted(cls, data, bot, credentials):
+ if not data:
+ return []
+
+ return [cls.de_json_decrypted(passport_file, bot, credentials.files[i])
+ for i, passport_file in enumerate(data)]
+
+ def get_file(self, timeout=None, **kwargs):
+ """
+ Wrapper over :attr:`telegram.Bot.get_file`. Will automatically assign the correct
+ credentials to the returned :class:`telegram.File` if originating from
+ :obj:`telegram.PassportData.decrypted_data`.
+
+ 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:
+ :class:`telegram.File`
+
+ Raises:
+ :class:`telegram.TelegramError`
+
+ """
+ file = self.bot.get_file(self.file_id, timeout=timeout, **kwargs)
+ file.set_credentials(self._credentials)
+ return file
diff --git a/telegram/user.py b/telegram/user.py
index 3c5b4b601..e140cfc02 100644
--- a/telegram/user.py
+++ b/telegram/user.py
@@ -203,6 +203,19 @@ class User(TelegramObject):
"""
return self.bot.send_document(self.id, *args, **kwargs)
+ def send_animation(self, *args, **kwargs):
+ """Shortcut for::
+
+ bot.send_animation(User.id, *args, **kwargs)
+
+ Where User is the current instance.
+
+ Returns:
+ :class:`telegram.Message`: On success, instance representing the message posted.
+
+ """
+ return self.bot.send_animation(self.id, *args, **kwargs)
+
def send_sticker(self, *args, **kwargs):
"""Shortcut for::
diff --git a/telegram/utils/request.py b/telegram/utils/request.py
index 6fda64397..dc306f1b0 100644
--- a/telegram/utils/request.py
+++ b/telegram/utils/request.py
@@ -17,11 +17,12 @@
# 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 methods to make POST and GET requests."""
+import logging
import os
import socket
import sys
-import logging
import warnings
+from builtins import str # For PY2
try:
import ujson as json
@@ -40,12 +41,14 @@ except ImportError: # pragma: no cover
"how to properly install.")
raise
-from telegram import (InputFile, TelegramError)
+from telegram import (InputFile, TelegramError, InputMedia)
from telegram.error import (Unauthorized, NetworkError, TimedOut, BadRequest, ChatMigrated,
RetryAfter, InvalidToken)
logging.getLogger('urllib3').setLevel(logging.WARNING)
+USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)'
+
class Request(object):
"""
@@ -83,9 +86,12 @@ class Request(object):
# TODO: Support other platforms like mac and windows.
if 'linux' in sys.platform:
- sockopts.append((socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 120))
- sockopts.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 30))
- sockopts.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 8))
+ sockopts.append((socket.IPPROTO_TCP,
+ socket.TCP_KEEPIDLE, 120)) # pylint: disable=no-member
+ sockopts.append((socket.IPPROTO_TCP,
+ socket.TCP_KEEPINTVL, 30)) # pylint: disable=no-member
+ sockopts.append((socket.IPPROTO_TCP,
+ socket.TCP_KEEPCNT, 8)) # pylint: disable=no-member
self._con_pool_size = con_pool_size
@@ -190,6 +196,8 @@ class Request(object):
if 'headers' not in kwargs:
kwargs['headers'] = {}
kwargs['headers']['connection'] = 'keep-alive'
+ # Also set our user agent
+ kwargs['headers']['user-agent'] = USER_AGENT
try:
resp = self._con_pool.request(*args, **kwargs)
@@ -264,18 +272,41 @@ class Request(object):
if timeout is not None:
urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout)
- if InputFile.is_inputfile(data):
- data = InputFile(data)
- result = self._request_wrapper(
- 'POST', url, body=data.to_form(), headers=data.headers, **urlopen_kwargs)
+ # Are we uploading files?
+ files = False
+
+ for key, val in data.copy().items():
+ if isinstance(val, InputFile):
+ # Convert the InputFile to urllib3 field format
+ data[key] = val.field_tuple
+ files = True
+ elif isinstance(val, (float, int)):
+ # Urllib3 doesn't like floats it seems
+ data[key] = str(val)
+ elif key == 'media':
+ # One media or multiple
+ if isinstance(val, InputMedia):
+ # Attach and set val to attached name
+ data[key] = val.to_json()
+ if isinstance(val.media, InputFile):
+ data[val.media.attach] = val.media.field_tuple
+ else:
+ # Attach and set val to attached name for all
+ media = []
+ for m in val:
+ media.append(m.to_dict())
+ if isinstance(m.media, InputFile):
+ data[m.media.attach] = m.media.field_tuple
+ data[key] = json.dumps(media)
+ files = True
+
+ # Use multipart upload if we're uploading files, otherwise use JSON
+ if files:
+ result = self._request_wrapper('POST', url, fields=data, **urlopen_kwargs)
else:
- data = json.dumps(data)
- result = self._request_wrapper(
- 'POST',
- url,
- body=data.encode(),
- headers={'Content-Type': 'application/json'},
- **urlopen_kwargs)
+ result = self._request_wrapper('POST', url,
+ body=json.dumps(data).encode('utf-8'),
+ headers={'Content-Type': 'application/json'})
return self._parse(result)
diff --git a/tests/conftest.py b/tests/conftest.py
index cf500995c..37c5698d8 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -26,7 +26,7 @@ from time import sleep
import pytest
from telegram import Bot
-from telegram.ext import Dispatcher, JobQueue
+from telegram.ext import Dispatcher, JobQueue, Updater
from tests.bots import get_bot
TRAVIS = os.getenv('TRAVIS', False)
@@ -34,6 +34,10 @@ TRAVIS = os.getenv('TRAVIS', False)
if TRAVIS:
pytest_plugins = ['tests.travis_fold']
+# THIS KEY IS OBVIOUSLY COMPROMISED
+# DO NOT USE IN PRODUCTION!
+PRIVATE_KEY = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEA0AvEbNaOnfIL3GjB8VI4M5IaWe+GcK8eSPHkLkXREIsaddum\r\nwPBm/+w8lFYdnY+O06OEJrsaDtwGdU//8cbGJ/H/9cJH3dh0tNbfszP7nTrQD+88\r\nydlcYHzClaG8G+oTe9uEZSVdDXj5IUqR0y6rDXXb9tC9l+oSz+ShYg6+C4grAb3E\r\nSTv5khZ9Zsi/JEPWStqNdpoNuRh7qEYc3t4B/a5BH7bsQENyJSc8AWrfv+drPAEe\r\njQ8xm1ygzWvJp8yZPwOIYuL+obtANcoVT2G2150Wy6qLC0bD88Bm40GqLbSazueC\r\nRHZRug0B9rMUKvKc4FhG4AlNzBCaKgIcCWEqKwIDAQABAoIBACcIjin9d3Sa3S7V\r\nWM32JyVF3DvTfN3XfU8iUzV7U+ZOswA53eeFM04A/Ly4C4ZsUNfUbg72O8Vd8rg/\r\n8j1ilfsYpHVvphwxaHQlfIMa1bKCPlc/A6C7b2GLBtccKTbzjARJA2YWxIaqk9Nz\r\nMjj1IJK98i80qt29xRnMQ5sqOO3gn2SxTErvNchtBiwOH8NirqERXig8VCY6fr3n\r\nz7ZImPU3G/4qpD0+9ULrt9x/VkjqVvNdK1l7CyAuve3D7ha3jPMfVHFtVH5gqbyp\r\nKotyIHAyD+Ex3FQ1JV+H7DkP0cPctQiss7OiO9Zd9C1G2OrfQz9el7ewAPqOmZtC\r\nKjB3hUECgYEA/4MfKa1cvaCqzd3yUprp1JhvssVkhM1HyucIxB5xmBcVLX2/Kdhn\r\nhiDApZXARK0O9IRpFF6QVeMEX7TzFwB6dfkyIePsGxputA5SPbtBlHOvjZa8omMl\r\nEYfNa8x/mJkvSEpzvkWPascuHJWv1cEypqphu/70DxubWB5UKo/8o6cCgYEA0HFy\r\ncgwPMB//nltHGrmaQZPFT7/Qgl9ErZT3G9S8teWY4o4CXnkdU75tBoKAaJnpSfX3\r\nq8VuRerF45AFhqCKhlG4l51oW7TUH50qE3GM+4ivaH5YZB3biwQ9Wqw+QyNLAh/Q\r\nnS4/Wwb8qC9QuyEgcCju5lsCaPEXZiZqtPVxZd0CgYEAshBG31yZjO0zG1TZUwfy\r\nfN3euc8mRgZpSdXIHiS5NSyg7Zr8ZcUSID8jAkJiQ3n3OiAsuq1MGQ6kNa582kLT\r\nFPQdI9Ea8ahyDbkNR0gAY9xbM2kg/Gnro1PorH9PTKE0ekSodKk1UUyNrg4DBAwn\r\nqE6E3ebHXt/2WmqIbUD653ECgYBQCC8EAQNX3AFegPd1GGxU33Lz4tchJ4kMCNU0\r\nN2NZh9VCr3nTYjdTbxsXU8YP44CCKFG2/zAO4kymyiaFAWEOn5P7irGF/JExrjt4\r\nibGy5lFLEq/HiPtBjhgsl1O0nXlwUFzd7OLghXc+8CPUJaz5w42unqT3PBJa40c3\r\nQcIPdQKBgBnSb7BcDAAQ/Qx9juo/RKpvhyeqlnp0GzPSQjvtWi9dQRIu9Pe7luHc\r\nm1Img1EO1OyE3dis/rLaDsAa2AKu1Yx6h85EmNjavBqP9wqmFa0NIQQH8fvzKY3/\r\nP8IHY6009aoamLqYaexvrkHVq7fFKiI6k8myMJ6qblVNFv14+KXU\r\n-----END RSA PRIVATE KEY-----" # noqa: E501
+
@pytest.fixture(scope='session')
def bot_info():
@@ -42,7 +46,7 @@ def bot_info():
@pytest.fixture(scope='session')
def bot(bot_info):
- return Bot(bot_info['token'])
+ return Bot(bot_info['token'], private_key=PRIVATE_KEY)
@pytest.fixture(scope='session')
@@ -105,6 +109,28 @@ def dp(_dp):
Dispatcher._Dispatcher__singleton_semaphore.release()
+@pytest.fixture(scope='function')
+def updater(bot):
+ up = Updater(bot=bot, workers=2)
+ yield up
+ if up.running:
+ up.stop()
+
+
+@pytest.fixture(scope='function')
+def thumb_file():
+ f = open(u'tests/data/thumb.jpg', 'rb')
+ yield f
+ f.close()
+
+
+@pytest.fixture(scope='class')
+def class_thumb_file():
+ f = open(u'tests/data/thumb.jpg', 'rb')
+ yield f
+ f.close()
+
+
def pytest_configure(config):
if sys.version_info >= (3,):
config.addinivalue_line('filterwarnings', 'ignore::ResourceWarning')
diff --git a/tests/data/thumb.jpg b/tests/data/thumb.jpg
new file mode 100644
index 000000000..1261d43e6
Binary files /dev/null and b/tests/data/thumb.jpg differ
diff --git a/tests/test_animation.py b/tests/test_animation.py
index a8493c419..ca95c15ab 100644
--- a/tests/test_animation.py
+++ b/tests/test_animation.py
@@ -18,58 +18,109 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
+from flaky import flaky
from telegram import PhotoSize, Animation, Voice
-@pytest.fixture(scope='class')
-def thumb():
- return PhotoSize(height=50, file_size=1613, file_id='AAQEABPQUWQZAAT7gZuQAAH0bd93VwACAg',
- width=90)
+@pytest.fixture(scope='function')
+def animation_file():
+ f = open('tests/data/game.gif', 'rb')
+ yield f
+ f.close()
@pytest.fixture(scope='class')
-def animation(thumb, bot):
- return Animation(TestAnimation.animation_file_id, thumb=thumb.to_dict(),
- file_name=TestAnimation.file_name, mime_type=TestAnimation.mime_type,
- file_size=TestAnimation.file_size, bot=bot)
+def animation(bot, chat_id):
+ with open('tests/data/game.gif', 'rb') as f:
+ return bot.send_animation(chat_id, animation=f, timeout=50,
+ thumb=open('tests/data/thumb.jpg', 'rb')).animation
class TestAnimation(object):
- animation_file_id = 'CgADBAADFQEAAny4rAUgukhiTv2TWwI'
+ animation_file_id = 'CgADAQADngIAAuyVeEez0xRovKi9VAI'
+ width = 320
+ height = 180
+ duration = 1
file_name = 'game.gif.mp4'
mime_type = 'video/mp4'
- file_size = 4008
+ file_size = 4135
+ caption = "Test *animation*"
- def test_de_json(self, bot, thumb):
+ def test_creation(self, animation):
+ assert isinstance(animation, Animation)
+ assert isinstance(animation.file_id, str)
+ assert animation.file_id is not ''
+
+ def test_expected_values(self, animation):
+ assert animation.file_size == self.file_size
+ assert animation.mime_type == self.mime_type
+ assert animation.file_name == self.file_name
+ assert isinstance(animation.thumb, PhotoSize)
+
+ @flaky(3, 1)
+ @pytest.mark.timeout(10)
+ def test_send_all_args(self, bot, chat_id, animation_file, animation, thumb_file):
+ message = bot.send_animation(chat_id, animation_file, duration=self.duration,
+ width=self.width, height=self.height, caption=self.caption,
+ parse_mode='Markdown', disable_notification=False,
+ filename=self.file_name, thumb=thumb_file)
+
+ assert isinstance(message.animation, Animation)
+ assert isinstance(message.animation.file_id, str)
+ assert message.animation.file_id != ''
+ assert message.animation.file_name == animation.file_name
+ assert message.animation.mime_type == animation.mime_type
+ assert message.animation.file_size == animation.file_size
+ assert message.animation.thumb.width == 50
+ assert message.animation.thumb.height == 50
+
+ @flaky(3, 1)
+ def test_resend(self, bot, chat_id, animation):
+ message = bot.send_animation(chat_id, animation.file_id)
+
+ assert isinstance(message.animation, Animation)
+ assert isinstance(message.animation.file_id, str)
+ assert message.animation.file_id != ''
+ assert message.animation.file_name == animation.file_name
+ assert message.animation.mime_type == animation.mime_type
+ assert message.animation.file_size == animation.file_size
+
+ def test_de_json(self, bot, animation):
json_dict = {
'file_id': self.animation_file_id,
- 'thumb': thumb.to_dict(),
+ 'width': self.width,
+ 'height': self.height,
+ 'duration': self.duration,
+ 'thumb': animation.thumb.to_dict(),
'file_name': self.file_name,
'mime_type': self.mime_type,
'file_size': self.file_size
}
animation = Animation.de_json(json_dict, bot)
assert animation.file_id == self.animation_file_id
- assert animation.thumb == thumb
+ assert animation.thumb == animation.thumb
assert animation.file_name == self.file_name
assert animation.mime_type == self.mime_type
assert animation.file_size == self.file_size
- def test_to_dict(self, animation, thumb):
+ def test_to_dict(self, animation):
animation_dict = animation.to_dict()
assert isinstance(animation_dict, dict)
assert animation_dict['file_id'] == animation.file_id
- assert animation_dict['thumb'] == thumb.to_dict()
+ assert animation_dict['width'] == animation.width
+ assert animation_dict['height'] == animation.height
+ assert animation_dict['duration'] == animation.duration
+ assert animation_dict['thumb'] == animation.thumb.to_dict()
assert animation_dict['file_name'] == animation.file_name
assert animation_dict['mime_type'] == animation.mime_type
assert animation_dict['file_size'] == animation.file_size
def test_equality(self):
- a = Animation(self.animation_file_id)
- b = Animation(self.animation_file_id)
- d = Animation('')
+ a = Animation(self.animation_file_id, self.height, self.width, self.duration)
+ b = Animation(self.animation_file_id, self.height, self.width, self.duration)
+ d = Animation('', 0, 0, 0)
e = Voice(self.animation_file_id, 0)
assert a == b
diff --git a/tests/test_audio.py b/tests/test_audio.py
index 768e4269a..e5a0bb73c 100644
--- a/tests/test_audio.py
+++ b/tests/test_audio.py
@@ -34,7 +34,8 @@ def audio_file():
@pytest.fixture(scope='class')
def audio(bot, chat_id):
with open('tests/data/telegram.mp3', 'rb') as f:
- return bot.send_audio(chat_id, audio=f, timeout=50).audio
+ return bot.send_audio(chat_id, audio=f, timeout=50,
+ thumb=open('tests/data/thumb.jpg', 'rb')).audio
class TestAudio(object):
@@ -47,6 +48,9 @@ class TestAudio(object):
audio_file_url = 'https://goo.gl/3En24v'
mime_type = 'audio/mpeg'
file_size = 122920
+ thumb_file_size = 2744
+ thumb_width = 50
+ thumb_height = 50
def test_creation(self, audio):
# Make sure file has been uploaded.
@@ -60,14 +64,17 @@ class TestAudio(object):
assert audio.title is None
assert audio.mime_type == self.mime_type
assert audio.file_size == self.file_size
+ assert audio.thumb.file_size == self.thumb_file_size
+ assert audio.thumb.width == self.thumb_width
+ assert audio.thumb.height == self.thumb_height
@flaky(3, 1)
@pytest.mark.timeout(10)
- def test_send_all_args(self, bot, chat_id, audio_file):
+ def test_send_all_args(self, bot, chat_id, audio_file, thumb_file):
message = bot.send_audio(chat_id, audio=audio_file, caption=self.caption,
duration=self.duration, performer=self.performer,
title=self.title, disable_notification=False,
- parse_mode='Markdown')
+ parse_mode='Markdown', thumb=thumb_file)
assert message.caption == self.caption.replace('*', '')
@@ -79,6 +86,9 @@ class TestAudio(object):
assert message.audio.title == self.title
assert message.audio.mime_type == self.mime_type
assert message.audio.file_size == self.file_size
+ assert message.audio.thumb.file_size == self.thumb_file_size
+ assert message.audio.thumb.width == self.thumb_width
+ assert message.audio.thumb.height == self.thumb_height
@flaky(3, 1)
@pytest.mark.timeout(10)
@@ -122,14 +132,15 @@ class TestAudio(object):
message = bot.send_audio(audio=audio, chat_id=chat_id)
assert message
- def test_de_json(self, bot):
+ def test_de_json(self, bot, audio):
json_dict = {'file_id': 'not a file id',
'duration': self.duration,
'performer': self.performer,
'title': self.title,
'caption': self.caption,
'mime_type': self.mime_type,
- 'file_size': self.file_size}
+ 'file_size': self.file_size,
+ 'thumb': audio.thumb.to_dict()}
json_audio = Audio.de_json(json_dict, bot)
assert json_audio.file_id == 'not a file id'
@@ -138,6 +149,7 @@ class TestAudio(object):
assert json_audio.title == self.title
assert json_audio.mime_type == self.mime_type
assert json_audio.file_size == self.file_size
+ assert json_audio.thumb == audio.thumb
def test_to_dict(self, audio):
audio_dict = audio.to_dict()
diff --git a/tests/test_bot.py b/tests/test_bot.py
index 07d9f1493..0a6ba1bf3 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -104,8 +104,9 @@ class TestBot(object):
# Considering that the first message is old enough
bot.delete_message(chat_id=chat_id, message_id=1)
- # send_photo, send_audio, send_document, send_sticker, send_video, send_voice, send_video_note
- # and send_media_group are tested in their respective test modules. No need to duplicate here.
+ # send_photo, send_audio, send_document, send_sticker, send_video, send_voice, send_video_note,
+ # send_media_group and send_animation are tested in their respective test modules. No need to
+ # duplicate here.
@flaky(3, 1)
@pytest.mark.timeout(10)
@@ -114,14 +115,19 @@ class TestBot(object):
latitude = -23.691288
title = 'title'
address = 'address'
+ foursquare_id = 'foursquare id'
+ foursquare_type = 'foursquare type'
message = bot.send_venue(chat_id=chat_id, title=title, address=address, latitude=latitude,
- longitude=longitude)
+ longitude=longitude, foursquare_id=foursquare_id,
+ foursquare_type=foursquare_type)
assert message.venue
assert message.venue.title == title
assert message.venue.address == address
assert message.venue.location.latitude == latitude
assert message.venue.location.longitude == longitude
+ assert message.venue.foursquare_id == foursquare_id
+ assert message.venue.foursquare_type == foursquare_type
@flaky(3, 1)
@pytest.mark.timeout(10)
@@ -258,6 +264,8 @@ class TestBot(object):
assert message.caption == 'new_caption'
+ # edit_message_media is tested in test_inputmedia
+
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_edit_message_caption_with_parse_mode(self, bot, media_message):
diff --git a/tests/test_chat.py b/tests/test_chat.py
index f88162de0..a108ba7e8 100644
--- a/tests/test_chat.py
+++ b/tests/test_chat.py
@@ -189,6 +189,13 @@ class TestChat(object):
monkeypatch.setattr('telegram.Bot.send_voice', test)
assert chat.send_voice('test_voice')
+ def test_instance_method_send_animation(self, monkeypatch, chat):
+ def test(*args, **kwargs):
+ return args[1] == chat.id and args[2] == 'test_animation'
+
+ monkeypatch.setattr('telegram.Bot.send_animation', test)
+ assert chat.send_animation('test_animation')
+
def test_equality(self):
a = Chat(self.id, self.title, self.type)
b = Chat(self.id, self.title, self.type)
diff --git a/tests/test_document.py b/tests/test_document.py
index 27aef512b..406bfc971 100644
--- a/tests/test_document.py
+++ b/tests/test_document.py
@@ -45,7 +45,7 @@ class TestDocument(object):
file_name = 'telegram.png'
thumb_file_size = 2364
thumb_width = 90
- thumb_heigth = 90
+ thumb_height = 90
def test_creation(self, document):
assert isinstance(document, Document)
@@ -58,14 +58,14 @@ class TestDocument(object):
assert document.file_name == self.file_name
assert document.thumb.file_size == self.thumb_file_size
assert document.thumb.width == self.thumb_width
- assert document.thumb.height == self.thumb_heigth
+ assert document.thumb.height == self.thumb_height
@flaky(3, 1)
@pytest.mark.timeout(10)
- def test_send_all_args(self, bot, chat_id, document_file, document):
+ def test_send_all_args(self, bot, chat_id, document_file, document, thumb_file):
message = bot.send_document(chat_id, document=document_file, caption=self.caption,
disable_notification=False, filename='telegram_custom.png',
- parse_mode='Markdown')
+ parse_mode='Markdown', thumb=thumb_file)
assert isinstance(message.document, Document)
assert isinstance(message.document.file_id, str)
@@ -74,8 +74,9 @@ class TestDocument(object):
assert message.document.file_name == 'telegram_custom.png'
assert message.document.mime_type == document.mime_type
assert message.document.file_size == document.file_size
- assert message.document.thumb == document.thumb
assert message.caption == self.caption.replace('*', '')
+ assert message.document.thumb.width == 50
+ assert message.document.thumb.height == 50
@flaky(3, 1)
@pytest.mark.timeout(10)
diff --git a/tests/test_encryptedcredentials.py b/tests/test_encryptedcredentials.py
new file mode 100644
index 000000000..301af577e
--- /dev/null
+++ b/tests/test_encryptedcredentials.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 EncryptedCredentials, PassportElementError
+
+
+@pytest.fixture(scope='class')
+def encrypted_credentials():
+ return EncryptedCredentials(TestEncryptedCredentials.data,
+ TestEncryptedCredentials.hash,
+ TestEncryptedCredentials.secret)
+
+
+class TestEncryptedCredentials(object):
+ data = 'data'
+ hash = 'hash'
+ secret = 'secret'
+
+ def test_expected_values(self, encrypted_credentials):
+ assert encrypted_credentials.data == self.data
+ assert encrypted_credentials.hash == self.hash
+ assert encrypted_credentials.secret == self.secret
+
+ def test_to_dict(self, encrypted_credentials):
+ encrypted_credentials_dict = encrypted_credentials.to_dict()
+
+ assert isinstance(encrypted_credentials_dict, dict)
+ assert (encrypted_credentials_dict['data'] ==
+ encrypted_credentials.data)
+ assert (encrypted_credentials_dict['hash'] ==
+ encrypted_credentials.hash)
+ assert (encrypted_credentials_dict['secret'] ==
+ encrypted_credentials.secret)
+
+ def test_equality(self):
+ a = EncryptedCredentials(self.data, self.hash, self.secret)
+ b = EncryptedCredentials(self.data, self.hash, self.secret)
+ c = EncryptedCredentials(self.data, '', '')
+ d = EncryptedCredentials('', self.hash, '')
+ e = EncryptedCredentials('', '', self.secret)
+ f = PassportElementError('source', 'type', 'message')
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ assert a != c
+ assert hash(a) != hash(c)
+
+ assert a != d
+ assert hash(a) != hash(d)
+
+ assert a != e
+ assert hash(a) != hash(e)
+
+ assert a != f
+ assert hash(a) != hash(f)
diff --git a/tests/test_encryptedpassportelement.py b/tests/test_encryptedpassportelement.py
new file mode 100644
index 000000000..10e53cf3c
--- /dev/null
+++ b/tests/test_encryptedpassportelement.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 EncryptedPassportElement, PassportFile, PassportElementError
+
+
+@pytest.fixture(scope='class')
+def encrypted_passport_element():
+ return EncryptedPassportElement(TestEncryptedPassportElement.type,
+ data=TestEncryptedPassportElement.data,
+ phone_number=TestEncryptedPassportElement.phone_number,
+ email=TestEncryptedPassportElement.email,
+ files=TestEncryptedPassportElement.files,
+ front_side=TestEncryptedPassportElement.front_side,
+ reverse_side=TestEncryptedPassportElement.reverse_side,
+ selfie=TestEncryptedPassportElement.selfie)
+
+
+class TestEncryptedPassportElement(object):
+ type = 'type'
+ data = 'data'
+ phone_number = 'phone_number'
+ email = 'email'
+ files = [PassportFile('file_id', 50, 0)]
+ front_side = PassportFile('file_id', 50, 0)
+ reverse_side = PassportFile('file_id', 50, 0)
+ selfie = PassportFile('file_id', 50, 0)
+
+ def test_expected_values(self, encrypted_passport_element):
+ assert encrypted_passport_element.type == self.type
+ assert encrypted_passport_element.data == self.data
+ assert encrypted_passport_element.phone_number == self.phone_number
+ assert encrypted_passport_element.email == self.email
+ assert encrypted_passport_element.files == self.files
+ assert encrypted_passport_element.front_side == self.front_side
+ assert encrypted_passport_element.reverse_side == self.reverse_side
+ assert encrypted_passport_element.selfie == self.selfie
+
+ def test_to_dict(self, encrypted_passport_element):
+ encrypted_passport_element_dict = encrypted_passport_element.to_dict()
+
+ assert isinstance(encrypted_passport_element_dict, dict)
+ assert (encrypted_passport_element_dict['type'] ==
+ encrypted_passport_element.type)
+ assert (encrypted_passport_element_dict['data'] ==
+ encrypted_passport_element.data)
+ assert (encrypted_passport_element_dict['phone_number'] ==
+ encrypted_passport_element.phone_number)
+ assert (encrypted_passport_element_dict['email'] ==
+ encrypted_passport_element.email)
+ assert isinstance(encrypted_passport_element_dict['files'], list)
+ assert (encrypted_passport_element_dict['front_side'] ==
+ encrypted_passport_element.front_side.to_dict())
+ assert (encrypted_passport_element_dict['reverse_side'] ==
+ encrypted_passport_element.reverse_side.to_dict())
+ assert (encrypted_passport_element_dict['selfie'] ==
+ encrypted_passport_element.selfie.to_dict())
+
+ def test_equality(self):
+ a = EncryptedPassportElement(self.type, data=self.data)
+ b = EncryptedPassportElement(self.type, data=self.data)
+ c = EncryptedPassportElement(self.data, '')
+ d = PassportElementError('source', 'type', 'message')
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ assert a != c
+ assert hash(a) != hash(c)
+
+ assert a != d
+ assert hash(a) != hash(d)
diff --git a/tests/test_filters.py b/tests/test_filters.py
index cf8c164e7..df88213cf 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -188,6 +188,11 @@ class TestFilters(object):
assert Filters.document.category("application/")(message)
assert Filters.document.mime_type("application/x-sh")(message)
+ def test_filters_animation(self, message):
+ assert not Filters.animation(message)
+ message.animation = 'test'
+ assert Filters.animation(message)
+
def test_filters_photo(self, message):
assert not Filters.photo(message)
message.photo = 'test'
@@ -396,6 +401,11 @@ class TestFilters(object):
message.successful_payment = 'test'
assert Filters.successful_payment(message)
+ def test_filters_passport_data(self, message):
+ assert not Filters.passport_data(message)
+ message.passport_data = 'test'
+ assert Filters.passport_data(message)
+
def test_language_filter_single(self, message):
message.from_user.language_code = 'en_US'
assert (Filters.language('en_US'))(message)
diff --git a/tests/test_game.py b/tests/test_game.py
index 4d586e778..6f293a24e 100644
--- a/tests/test_game.py
+++ b/tests/test_game.py
@@ -39,7 +39,7 @@ class TestGame(object):
text = (b'\\U0001f469\\u200d\\U0001f469\\u200d\\U0001f467'
b'\\u200d\\U0001f467\\U0001f431http://google.com').decode('unicode-escape')
text_entities = [MessageEntity(13, 17, MessageEntity.URL)]
- animation = Animation('blah')
+ animation = Animation('blah', 320, 180, 1)
def test_de_json_required(self, bot):
json_dict = {
diff --git a/tests/test_inlinequeryresultvenue.py b/tests/test_inlinequeryresultvenue.py
index 5e0e2c651..dd9f57bd0 100644
--- a/tests/test_inlinequeryresultvenue.py
+++ b/tests/test_inlinequeryresultvenue.py
@@ -32,6 +32,7 @@ def inline_query_result_venue():
TestInlineQueryResultVenue.title,
TestInlineQueryResultVenue.address,
foursquare_id=TestInlineQueryResultVenue.foursquare_id,
+ foursquare_type=TestInlineQueryResultVenue.foursquare_type,
thumb_url=TestInlineQueryResultVenue.thumb_url,
thumb_width=TestInlineQueryResultVenue.thumb_width,
thumb_height=TestInlineQueryResultVenue.thumb_height,
@@ -47,6 +48,7 @@ class TestInlineQueryResultVenue(object):
title = 'title'
address = 'address'
foursquare_id = 'foursquare id'
+ foursquare_type = 'foursquare type'
thumb_url = 'thumb url'
thumb_width = 10
thumb_height = 15
@@ -61,6 +63,7 @@ class TestInlineQueryResultVenue(object):
assert inline_query_result_venue.title == self.title
assert inline_query_result_venue.address == self.address
assert inline_query_result_venue.foursquare_id == self.foursquare_id
+ assert inline_query_result_venue.foursquare_type == self.foursquare_type
assert inline_query_result_venue.thumb_url == self.thumb_url
assert inline_query_result_venue.thumb_width == self.thumb_width
assert inline_query_result_venue.thumb_height == self.thumb_height
@@ -80,6 +83,8 @@ class TestInlineQueryResultVenue(object):
assert inline_query_result_venue_dict['address'] == inline_query_result_venue.address
assert (inline_query_result_venue_dict['foursquare_id'] ==
inline_query_result_venue.foursquare_id)
+ assert (inline_query_result_venue_dict['foursquare_type'] ==
+ inline_query_result_venue.foursquare_type)
assert inline_query_result_venue_dict['thumb_url'] == inline_query_result_venue.thumb_url
assert (inline_query_result_venue_dict['thumb_width'] ==
inline_query_result_venue.thumb_width)
diff --git a/tests/test_inputfile.py b/tests/test_inputfile.py
index 82837872e..fd34c841b 100644
--- a/tests/test_inputfile.py
+++ b/tests/test_inputfile.py
@@ -20,6 +20,7 @@
import os
import subprocess
import sys
+from io import BytesIO
from telegram import InputFile
@@ -34,7 +35,7 @@ class TestInputFile(object):
cmd = ['cat', self.png]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=(sys.platform == 'win32'))
- in_file = InputFile({'photo': proc.stdout})
+ in_file = InputFile(proc.stdout)
assert in_file.input_file_content == open(self.png, 'rb').read()
assert in_file.mimetype == 'image/png'
@@ -46,3 +47,19 @@ class TestInputFile(object):
# This exception may be thrown if the process has finished before we had the chance
# to kill it.
pass
+
+ def test_mimetypes(self):
+ # Only test a few to make sure logic works okay
+ assert InputFile(open('tests/data/telegram.jpg', 'rb')).mimetype == 'image/jpeg'
+ if sys.version_info >= (3, 5):
+ assert InputFile(open('tests/data/telegram.webp', 'rb')).mimetype == 'image/webp'
+ assert InputFile(open('tests/data/telegram.mp3', 'rb')).mimetype == 'audio/mpeg'
+
+ # Test guess from file
+ assert InputFile(BytesIO(b'blah'), filename='tg.jpg').mimetype == 'image/jpeg'
+ assert InputFile(BytesIO(b'blah'), filename='tg.mp3').mimetype == 'audio/mpeg'
+
+ # Test fallback
+ assert (InputFile(BytesIO(b'blah'), filename='tg.notaproperext').mimetype ==
+ 'application/octet-stream')
+ assert InputFile(BytesIO(b'blah')).mimetype == 'application/octet-stream'
diff --git a/tests/test_inputmedia.py b/tests/test_inputmedia.py
index fc2027926..38de57aa3 100644
--- a/tests/test_inputmedia.py
+++ b/tests/test_inputmedia.py
@@ -19,29 +19,69 @@
import pytest
from flaky import flaky
-from telegram import InputMediaVideo, InputMediaPhoto, Message
-from .test_video import video, video_file # noqa: F401
+from telegram import (InputMediaVideo, InputMediaPhoto, InputMediaAnimation, Message, InputFile,
+ InputMediaAudio, InputMediaDocument)
+# noinspection PyUnresolvedReferences
+from .test_animation import animation, animation_file # noqa: F401
+# noinspection PyUnresolvedReferences
+from .test_audio import audio, audio_file # noqa: F401
+# noinspection PyUnresolvedReferences
+from .test_document import document, document_file # noqa: F401
+# noinspection PyUnresolvedReferences
from .test_photo import _photo, photo_file, photo, thumb # noqa: F401
+# noinspection PyUnresolvedReferences
+from .test_video import video, video_file # noqa: F401
@pytest.fixture(scope='class')
-def input_media_video():
+def input_media_video(class_thumb_file):
return InputMediaVideo(media=TestInputMediaVideo.media,
caption=TestInputMediaVideo.caption,
width=TestInputMediaVideo.width,
height=TestInputMediaVideo.height,
duration=TestInputMediaVideo.duration,
parse_mode=TestInputMediaVideo.parse_mode,
+ thumb=class_thumb_file,
supports_streaming=TestInputMediaVideo.supports_streaming)
@pytest.fixture(scope='class')
-def input_media_photo():
+def input_media_photo(class_thumb_file):
return InputMediaPhoto(media=TestInputMediaPhoto.media,
caption=TestInputMediaPhoto.caption,
parse_mode=TestInputMediaPhoto.parse_mode)
+@pytest.fixture(scope='class')
+def input_media_animation(class_thumb_file):
+ return InputMediaAnimation(media=TestInputMediaAnimation.media,
+ caption=TestInputMediaAnimation.caption,
+ parse_mode=TestInputMediaAnimation.parse_mode,
+ width=TestInputMediaAnimation.width,
+ height=TestInputMediaAnimation.height,
+ thumb=class_thumb_file,
+ duration=TestInputMediaAnimation.duration)
+
+
+@pytest.fixture(scope='class')
+def input_media_audio(class_thumb_file):
+ return InputMediaAudio(media=TestInputMediaAudio.media,
+ caption=TestInputMediaAudio.caption,
+ duration=TestInputMediaAudio.duration,
+ performer=TestInputMediaAudio.performer,
+ title=TestInputMediaAudio.title,
+ thumb=class_thumb_file,
+ parse_mode=TestInputMediaAudio.parse_mode)
+
+
+@pytest.fixture(scope='class')
+def input_media_document(class_thumb_file):
+ return InputMediaDocument(media=TestInputMediaDocument.media,
+ caption=TestInputMediaDocument.caption,
+ thumb=class_thumb_file,
+ parse_mode=TestInputMediaDocument.parse_mode)
+
+
class TestInputMediaVideo(object):
type = "video"
media = "NOTAREALFILEID"
@@ -61,6 +101,7 @@ class TestInputMediaVideo(object):
assert input_media_video.duration == self.duration
assert input_media_video.parse_mode == self.parse_mode
assert input_media_video.supports_streaming == self.supports_streaming
+ assert isinstance(input_media_video.thumb, InputFile)
def test_to_dict(self, input_media_video):
input_media_video_dict = input_media_video.to_dict()
@@ -83,10 +124,12 @@ class TestInputMediaVideo(object):
assert input_media_video.duration == video.duration
assert input_media_video.caption == "test 3"
- def test_error_with_file(self, video_file): # noqa: F811
+ def test_with_video_file(self, video_file): # noqa: F811
# fixture found in test_video
- with pytest.raises(ValueError, match="file_id, url or Video"):
- InputMediaVideo(video_file)
+ input_media_video = InputMediaVideo(video_file, caption="test 3")
+ assert input_media_video.type == self.type
+ assert isinstance(input_media_video.media, InputFile)
+ assert input_media_video.caption == "test 3"
class TestInputMediaPhoto(object):
@@ -110,15 +153,140 @@ class TestInputMediaPhoto(object):
def test_with_photo(self, photo): # noqa: F811
# fixture found in test_photo
- imp = InputMediaPhoto(photo, caption="test 2")
- assert imp.type == self.type
- assert imp.media == photo.file_id
- assert imp.caption == "test 2"
+ input_media_photo = InputMediaPhoto(photo, caption="test 2")
+ assert input_media_photo.type == self.type
+ assert input_media_photo.media == photo.file_id
+ assert input_media_photo.caption == "test 2"
- def test_error_with_file(self, photo_file): # noqa: F811
+ def test_with_photo_file(self, photo_file): # noqa: F811
# fixture found in test_photo
- with pytest.raises(ValueError, match="file_id, url or PhotoSize"):
- InputMediaPhoto(photo_file)
+ input_media_photo = InputMediaPhoto(photo_file, caption="test 2")
+ assert input_media_photo.type == self.type
+ assert isinstance(input_media_photo.media, InputFile)
+ assert input_media_photo.caption == "test 2"
+
+
+class TestInputMediaAnimation(object):
+ type = "animation"
+ media = "NOTAREALFILEID"
+ caption = "My Caption"
+ parse_mode = 'Markdown'
+ width = 30
+ height = 30
+ duration = 1
+
+ def test_expected_values(self, input_media_animation):
+ assert input_media_animation.type == self.type
+ assert input_media_animation.media == self.media
+ assert input_media_animation.caption == self.caption
+ assert input_media_animation.parse_mode == self.parse_mode
+ assert isinstance(input_media_animation.thumb, InputFile)
+
+ def test_to_dict(self, input_media_animation):
+ input_media_animation_dict = input_media_animation.to_dict()
+ assert input_media_animation_dict['type'] == input_media_animation.type
+ assert input_media_animation_dict['media'] == input_media_animation.media
+ assert input_media_animation_dict['caption'] == input_media_animation.caption
+ assert input_media_animation_dict['parse_mode'] == input_media_animation.parse_mode
+ assert input_media_animation_dict['width'] == input_media_animation.width
+ assert input_media_animation_dict['height'] == input_media_animation.height
+ assert input_media_animation_dict['duration'] == input_media_animation.duration
+
+ def test_with_animation(self, animation): # noqa: F811
+ # fixture found in test_animation
+ input_media_animation = InputMediaAnimation(animation, caption="test 2")
+ assert input_media_animation.type == self.type
+ assert input_media_animation.media == animation.file_id
+ assert input_media_animation.caption == "test 2"
+
+ def test_with_animation_file(self, animation_file): # noqa: F811
+ # fixture found in test_animation
+ input_media_animation = InputMediaAnimation(animation_file, caption="test 2")
+ assert input_media_animation.type == self.type
+ assert isinstance(input_media_animation.media, InputFile)
+ assert input_media_animation.caption == "test 2"
+
+
+class TestInputMediaAudio(object):
+ type = "audio"
+ media = "NOTAREALFILEID"
+ caption = "My Caption"
+ duration = 3
+ performer = 'performer'
+ title = 'title'
+ parse_mode = 'HTML'
+
+ def test_expected_values(self, input_media_audio):
+ assert input_media_audio.type == self.type
+ assert input_media_audio.media == self.media
+ assert input_media_audio.caption == self.caption
+ assert input_media_audio.duration == self.duration
+ assert input_media_audio.performer == self.performer
+ assert input_media_audio.title == self.title
+ assert input_media_audio.parse_mode == self.parse_mode
+ assert isinstance(input_media_audio.thumb, InputFile)
+
+ def test_to_dict(self, input_media_audio):
+ input_media_audio_dict = input_media_audio.to_dict()
+ assert input_media_audio_dict['type'] == input_media_audio.type
+ assert input_media_audio_dict['media'] == input_media_audio.media
+ assert input_media_audio_dict['caption'] == input_media_audio.caption
+ assert input_media_audio_dict['duration'] == input_media_audio.duration
+ assert input_media_audio_dict['performer'] == input_media_audio.performer
+ assert input_media_audio_dict['title'] == input_media_audio.title
+ assert input_media_audio_dict['parse_mode'] == input_media_audio.parse_mode
+
+ def test_with_audio(self, audio): # noqa: F811
+ # fixture found in test_audio
+ input_media_audio = InputMediaAudio(audio, caption="test 3")
+ assert input_media_audio.type == self.type
+ assert input_media_audio.media == audio.file_id
+ assert input_media_audio.duration == audio.duration
+ assert input_media_audio.performer == audio.performer
+ assert input_media_audio.title == audio.title
+ assert input_media_audio.caption == "test 3"
+
+ def test_with_audio_file(self, audio_file): # noqa: F811
+ # fixture found in test_audio
+ input_media_audio = InputMediaAudio(audio_file, caption="test 3")
+ assert input_media_audio.type == self.type
+ assert isinstance(input_media_audio.media, InputFile)
+ assert input_media_audio.caption == "test 3"
+
+
+class TestInputMediaDocument(object):
+ type = "document"
+ media = "NOTAREALFILEID"
+ caption = "My Caption"
+ parse_mode = 'HTML'
+
+ def test_expected_values(self, input_media_document):
+ assert input_media_document.type == self.type
+ assert input_media_document.media == self.media
+ assert input_media_document.caption == self.caption
+ assert input_media_document.parse_mode == self.parse_mode
+ assert isinstance(input_media_document.thumb, InputFile)
+
+ def test_to_dict(self, input_media_document):
+ input_media_document_dict = input_media_document.to_dict()
+ assert input_media_document_dict['type'] == input_media_document.type
+ assert input_media_document_dict['media'] == input_media_document.media
+ assert input_media_document_dict['caption'] == input_media_document.caption
+ assert input_media_document_dict['parse_mode'] == input_media_document.parse_mode
+
+ def test_with_document(self, document): # noqa: F811
+ # fixture found in test_document
+ input_media_document = InputMediaDocument(document, caption="test 3")
+ assert input_media_document.type == self.type
+ assert input_media_document.media == document.file_id
+ assert input_media_document.caption == "test 3"
+
+ def test_with_document_file(self, document_file): # noqa: F811
+ # fixture found in test_document
+ input_media_document = InputMediaDocument(document_file, caption="test 3")
+ assert input_media_document.type == self.type
+ assert isinstance(input_media_document.media, InputFile)
+ assert input_media_document.caption == "test 3"
@pytest.fixture(scope='function') # noqa: F811
@@ -148,6 +316,32 @@ class TestSendMediaGroup(object):
assert all([isinstance(mes, Message) for mes in messages])
assert all([mes.media_group_id == messages[0].media_group_id for mes in messages])
- @pytest.mark.skip(reason="Needs a rework to send new files")
- def test_send_media_group_new_files(self):
- pass
+ def test_send_media_group_new_files(self, bot, chat_id, video_file, photo_file, # noqa: F811
+ animation_file): # noqa: F811
+ messages = bot.send_media_group(chat_id, [
+ InputMediaVideo(video_file),
+ InputMediaPhoto(photo_file)
+ ])
+ assert isinstance(messages, list)
+ assert len(messages) == 2
+ assert all([isinstance(mes, Message) for mes in messages])
+ assert all([mes.media_group_id == messages[0].media_group_id for mes in messages])
+
+ @flaky(3, 1)
+ @pytest.mark.timeout(10)
+ def test_edit_message_media(self, bot, chat_id, media_group):
+ messages = bot.send_media_group(chat_id, media_group)
+ cid = messages[-1].chat.id
+ mid = messages[-1].message_id
+ new_message = bot.edit_message_media(chat_id=cid, message_id=mid, media=media_group[0])
+ assert isinstance(new_message, Message)
+
+ @flaky(3, 1)
+ @pytest.mark.timeout(10)
+ def test_edit_message_media_new_file(self, bot, chat_id, media_group, thumb_file):
+ messages = bot.send_media_group(chat_id, media_group)
+ cid = messages[-1].chat.id
+ mid = messages[-1].message_id
+ new_message = bot.edit_message_media(chat_id=cid, message_id=mid,
+ media=InputMediaPhoto(thumb_file))
+ assert isinstance(new_message, Message)
diff --git a/tests/test_inputvenuemessagecontent.py b/tests/test_inputvenuemessagecontent.py
index e8c5b8b28..b3766d85c 100644
--- a/tests/test_inputvenuemessagecontent.py
+++ b/tests/test_inputvenuemessagecontent.py
@@ -28,7 +28,8 @@ def input_venue_message_content():
TestInputVenueMessageContent.longitude,
TestInputVenueMessageContent.title,
TestInputVenueMessageContent.address,
- foursquare_id=TestInputVenueMessageContent.foursquare_id)
+ foursquare_id=TestInputVenueMessageContent.foursquare_id,
+ foursquare_type=TestInputVenueMessageContent.foursquare_type)
class TestInputVenueMessageContent(object):
@@ -37,6 +38,7 @@ class TestInputVenueMessageContent(object):
title = 'title'
address = 'address'
foursquare_id = 'foursquare id'
+ foursquare_type = 'foursquare type'
def test_expected_values(self, input_venue_message_content):
assert input_venue_message_content.longitude == self.longitude
@@ -44,6 +46,7 @@ class TestInputVenueMessageContent(object):
assert input_venue_message_content.title == self.title
assert input_venue_message_content.address == self.address
assert input_venue_message_content.foursquare_id == self.foursquare_id
+ assert input_venue_message_content.foursquare_type == self.foursquare_type
def test_to_dict(self, input_venue_message_content):
input_venue_message_content_dict = input_venue_message_content.to_dict()
@@ -57,3 +60,5 @@ class TestInputVenueMessageContent(object):
assert input_venue_message_content_dict['address'] == input_venue_message_content.address
assert (input_venue_message_content_dict['foursquare_id'] ==
input_venue_message_content.foursquare_id)
+ assert (input_venue_message_content_dict['foursquare_type'] ==
+ input_venue_message_content.foursquare_type)
diff --git a/tests/test_invoice.py b/tests/test_invoice.py
index 55be25089..1db049c8c 100644
--- a/tests/test_invoice.py
+++ b/tests/test_invoice.py
@@ -112,21 +112,11 @@ class TestInvoice(object):
def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token):
def test(_, url, data, **kwargs):
- return data['provider_data'] == '{"test_data": 123456789}'
+ return (data['provider_data'] == '{"test_data": 123456789}' or # Depends if using
+ data['provider_data'] == '{"test_data":123456789}') # ujson or not
monkeypatch.setattr('telegram.utils.request.Request.post', test)
assert bot.send_invoice(chat_id, self.title, self.description, self.payload,
provider_token, self.start_parameter, self.currency,
self.prices, provider_data={'test_data': 123456789})
-
- def test_send_nonesense_as_provider_data(self, monkeypatch, bot, chat_id, provider_token):
- def test(_, url, data, **kwargs):
- return True
-
- monkeypatch.setattr('telegram.utils.request.Request.post', test)
-
- with pytest.raises(TypeError):
- assert bot.send_invoice(chat_id, self.title, self.description, self.payload,
- provider_token, self.start_parameter, self.currency,
- self.prices, provider_data={'a', 'b', 'c'})
diff --git a/tests/test_message.py b/tests/test_message.py
index 2f1a04fb7..125f7be91 100644
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -21,9 +21,10 @@ from datetime import datetime
import pytest
from telegram import ParseMode
-from telegram import (Update, Message, User, MessageEntity, Chat, Audio, Document,
+from telegram import (Update, Message, User, MessageEntity, Chat, Audio, Document, Animation,
Game, PhotoSize, Sticker, Video, Voice, VideoNote, Contact, Location, Venue,
- Invoice, SuccessfulPayment)
+ Invoice, SuccessfulPayment, PassportData)
+from tests.test_passport import RAW_PASSPORT_DATA
@pytest.fixture(scope='class')
@@ -51,6 +52,8 @@ def message(bot):
'caption': 'audio_file'},
{'document': Document('document_id'),
'caption': 'document_file'},
+ {'animation': Animation('animation_id', 30, 30, 1),
+ 'caption': 'animation_file'},
{'game': Game('my_game', 'just my game',
[PhotoSize('game_photo_id', 30, 30), ])},
{'photo': [PhotoSize('photo_id', 50, 50)],
@@ -84,15 +87,17 @@ def message(bot):
{'author_signature': 'some_author_sign'},
{'photo': [PhotoSize('photo_id', 50, 50)],
'caption': 'photo_file',
- 'media_group_id': 1234443322222}
+ 'media_group_id': 1234443322222},
+ {'passport_data': PassportData.de_json(RAW_PASSPORT_DATA, None)}
],
ids=['forwarded_user', 'forwarded_channel', 'reply', 'edited', 'text',
- 'caption_entities', 'audio', 'document', 'game', 'photo', 'sticker', 'video',
- 'voice', 'video_note', 'new_members', 'contact', 'location', 'venue',
- 'left_member', 'new_title', 'new_photo', 'delete_photo', 'group_created',
- 'supergroup_created', 'channel_created', 'migrated_to', 'migrated_from',
- 'pinned', 'invoice', 'successful_payment', 'connected_website',
- 'forward_signature', 'author_signature', 'photo_from_media_group'])
+ 'caption_entities', 'audio', 'document', 'animation', 'game', 'photo',
+ 'sticker', 'video', 'voice', 'video_note', 'new_members', 'contact',
+ 'location', 'venue', 'left_member', 'new_title', 'new_photo', 'delete_photo',
+ 'group_created', 'supergroup_created', 'channel_created', 'migrated_to',
+ 'migrated_from', 'pinned', 'invoice', 'successful_payment',
+ 'connected_website', 'forward_signature', 'author_signature',
+ 'photo_from_media_group', 'passport_data'])
def message_params(bot, request):
return Message(message_id=TestMessage.id,
from_user=TestMessage.from_user,
@@ -295,8 +300,9 @@ class TestMessage(object):
assert message.link is None
def test_effective_attachment(self, message_params):
- for i in ('audio', 'game', 'document', 'photo', 'sticker', 'video', 'voice', 'video_note',
- 'contact', 'location', 'venue', 'invoice', 'invoice', 'successful_payment'):
+ for i in ('audio', 'game', 'document', 'animation', 'photo', 'sticker', 'video', 'voice',
+ 'video_note', 'contact', 'location', 'venue', 'invoice', 'invoice',
+ 'successful_payment'):
item = getattr(message_params, i, None)
if item:
break
@@ -424,6 +430,20 @@ class TestMessage(object):
assert message.reply_document(document='test_document')
assert message.reply_document(document='test_document', quote=True)
+ def test_reply_animation(self, monkeypatch, message):
+ def test(*args, **kwargs):
+ id = args[1] == message.chat_id
+ animation = kwargs['animation'] == 'test_animation'
+ if kwargs.get('reply_to_message_id'):
+ reply = kwargs['reply_to_message_id'] == message.message_id
+ else:
+ reply = True
+ return id and animation and reply
+
+ monkeypatch.setattr('telegram.Bot.send_animation', test)
+ assert message.reply_animation(animation='test_animation')
+ assert message.reply_animation(animation='test_animation', quote=True)
+
def test_reply_sticker(self, monkeypatch, message):
def test(*args, **kwargs):
id = args[1] == message.chat_id
@@ -558,6 +578,16 @@ class TestMessage(object):
monkeypatch.setattr('telegram.Bot.edit_message_caption', test)
assert message.edit_caption(caption='new caption')
+ def test_edit_media(self, monkeypatch, message):
+ def test(*args, **kwargs):
+ chat_id = kwargs['chat_id'] == message.chat_id
+ message_id = kwargs['message_id'] == message.message_id
+ media = kwargs['media'] == 'my_media'
+ return chat_id and message_id and media
+
+ monkeypatch.setattr('telegram.Bot.edit_message_media', test)
+ assert message.edit_media('my_media')
+
def test_edit_reply_markup(self, monkeypatch, message):
def test(*args, **kwargs):
chat_id = kwargs['chat_id'] == message.chat_id
diff --git a/tests/test_official.py b/tests/test_official.py
index b8afdf745..813c9cc5f 100644
--- a/tests/test_official.py
+++ b/tests/test_official.py
@@ -23,8 +23,8 @@ from platform import python_implementation
import certifi
import pytest
from bs4 import BeautifulSoup
-
from telegram.vendor.ptb_urllib3 import urllib3
+
import telegram
IGNORED_OBJECTS = ('ResponseParameters', 'CallbackGame')
@@ -99,6 +99,8 @@ def check_object(h4):
elif ((name.startswith('InlineQueryResult') or
name.startswith('InputMedia')) and field == 'type'):
continue
+ elif name.startswith('PassportElementError') and field == 'source':
+ continue
elif field == 'remove_keyboard':
continue
@@ -110,14 +112,15 @@ def check_object(h4):
ignored = IGNORED_PARAMETERS.copy()
if name == 'InputFile':
- ignored |= {'data'}
+ return
elif name == 'InlineQueryResult':
- ignored |= {'id'}
+ ignored |= {'id', 'type'}
elif name == 'User':
ignored |= {'type'} # TODO: Deprecation
-
- if name.startswith('InlineQueryResult'):
- ignored |= {'type'}
+ elif name in ('PassportFile', 'EncryptedPassportElement'):
+ ignored |= {'credentials'}
+ elif name == 'PassportElementError':
+ ignored |= {'message', 'type', 'source'}
assert (sig.parameters.keys() ^ checked) - ignored == set()
diff --git a/tests/test_passport.py b/tests/test_passport.py
new file mode 100644
index 000000000..b5e9eaf19
--- /dev/null
+++ b/tests/test_passport.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+# flake8: noqa: E501
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser Public License for more details.
+#
+# You should have received a copy of the GNU Lesser Public License
+# along with this program. If not, see [http://www.gnu.org/licenses/].
+from copy import deepcopy
+
+import pytest
+
+from telegram import (PassportData, PassportFile, Bot, File, PassportElementErrorSelfie,
+ PassportElementErrorDataField, Credentials, TelegramDecryptionError)
+
+RAW_PASSPORT_DATA = {'data': [{'type': 'personal_details',
+ 'data': 'tj3pNwOpN+ZHsyb6F3aJcNmEyPxrOtGTbu3waBlCQDNaQ9oJlkbXpw+HI3y9faq/+TCeB/WsS/2TxRXTKZw4zXvGP2UsfdRkJ2SQq6x+Ffe/oTF9/q8sWp2BwU3hHUOz7ec1/QrdPBhPJjbwSykEBNggPweiBVDZ0x/DWJ0guCkGT9smYGqog1vqlqbIWG7AWcxVy2fpUy9w/zDXjxj5WQ3lRpHJmi46s9xIHobNGGBvWw6/bGBCInMoovgqRCEu1sgz2QXF3wNiUzGFycEzLz7o+1htLys5n8Pdi9MG4RY='},
+ {'type': 'driver_license',
+ 'data': 'hOXQ/iHSGRDFXqql3yETA4AiP0mdlwmo9RtGS+Qg9E5okrN/yTcPNtBKb2fLA0posk35bvevN53cyJMHZnxErEFTSw/FQjPyRFdJUyjGNPeu4yOI73uk5eRVLTAlA2G0eN2azzfS/QOBGL19N3pHk9hMTZYPCBTDt89MHhRQS7Z3YWRSzFcFiEhktHv/ezgcg3EWtsUQ8K4J2445uoZzbB8wsQ6RM4ibn08RfjV6dNyVrj8jBGUpCBlA6iY60rFQM+LZ9ByI3OeS53bnIAMQC2rHHbV/wkzS6PbufOzjZgJq26aCLmC5YDomrpcrdvk0fvi6aEuBJEI3zcteh2fh/Q==',
+ 'selfie': {'file_id': 'DgADBAADEQQAAkopgFNr6oi-wISRtAI',
+ 'file_date': 1534074942},
+ 'reverse_side': {'file_id': 'DgADBAADNQQAAtoagFPf4wwmFZdmyQI',
+ 'file_date': 1534074942},
+ 'front_side': {'file_id': 'DgADBAADxwMAApnQgVPK2-ckL2eXVAI',
+ 'file_date': 1534074942}},
+ {'type': 'address',
+ 'data': 'j9SksVkSj128DBtZA+3aNjSFNirzv+R97guZaMgae4Gi0oDVNAF7twPR7j9VSmPedfJrEwL3O889Ei+a5F1xyLLyEI/qEBljvL70GFIhYGitS0JmNabHPHSZrjOl8b4s/0Z0Px2GpLO5siusTLQonimdUvu4UPjKquYISmlKEKhtmGATy+h+JDjNCYuOkhakeNw0Rk0BHgj0C3fCb7WZNQSyVb+2GTu6caR6eXf/AFwFp0TV3sRz3h0WIVPW8bna'},
+ {'type': 'utility_bill', 'files': [
+ {'file_id': 'DgADBAADLAMAAhwfgVMyfGa5Nr0LvAI',
+ 'file_date': 1534074988},
+ {'file_id': 'DgADBAADaQQAAsFxgVNVfLZuT-_3ZQI',
+ 'file_date': 1534074988}]},
+ {'type': 'email', 'email': 'fb3e3i47zt@dispostable.com'}],
+ 'credentials': {
+ 'data': 'uI/g4fJLVO6132t+yuBvKExTpTubinscH8KLVc8YPuo1SiXaBg4A6AaVdv60CPViMw8n+ShVWOTL6oN5Ye0+CC2/URZ0eeTMJcKkvJYRI0Q6YJ3aeeEzslwORKho0mGk5xSWpPV5LHXhRIlgUMA32NkbNuzzoij7OhqxvuDE50/0pAUKtxy69h+heAzxFj8+jYjgnOwgwNa6FGUG59oUozgpB98XrEeWGW+JIGE9fux8dSGkFhF2SjkmdW6b2Gexuq94TVGDgFigSvqMOgZj9slZI4UEdZBIGldPHrc8/+EuqWv+WKWU8hdj1dxfY/pNvonb8MwrQLrUyNafdLq5QD4Kmg3XPnZTI9bsYUf6xO9oKU42JezDcRbaCqKkUn6UceCkV2hHMJP4aVVI9Bad1k/rXlaDh8PUV19n6tct5UH8JfkBbGlj2uASI5lPasIvnWg9s1vsZ4ynE2YAuU4iotMkgmJkj1+JCl8Ul9uqzfiK0IbYfu57V82gUEjn6c4br49pY+Cvo9od9Lx8fSJCiXq+DdmDTfIZxpeFqEYeJi6/v0CjJni4CtS6LJGo9JUR2MlqOAiyOLomadlrZyzC2eLm8X6ouIXTxtbUqzxcTiH/r23NQ44YwJpMdDiulNuPg0tEyio8TokHj9APORiW+QIw4zLZFyBEorT1DSALm4AV3A1lIg0svOncDkOqa3ZydQ0tdysoXOq5Zsu+Qu/DQZQz5IGEHb7N9QW6KZDGfBOP5Ok04OxlXO8UaTZr6mysmu8jDOlqtitLKSCGDy0LPfITzehTqfEu0KrtmxI91zJtiNyE4g+oBzQsLwQ7vSp1xl7YNViDi8ea/upZdRavh3NthVD5TxyNJedt4Xp8q5/H5kSV/EFhumONPwBwOuN4KrsCQEaNtHZfYB8SH1v0rWbiFw3/owxCrCPqgEwMGnx+JTqsAplurIZfxsNz+57O4Hq10VVt7yhroq9UpIAR8C8hW58dcZBflWyQwG8HalYAaPJ/teSLP9o6MyiqrUkADbNBOFsUhIerq6Q0BnQ941sWg10BkG8oQSU6JdnZ1Ygml2gpFabBRXeeF5ijD0Y9Bq8D3vDnHKx/GziAK0aKlwSqfTmJ+C1hm7AxBekjtDavkBeq5bIOtxYaXqrtunQFAZRCrcnQEg0DPY6dYw+jmOR0NuHRMU5KeZ0994Jn0TRGiUoJtLp1MHJ2D1MCWG3Fp9Ps7vB46a9XiGYHJUdJjHsavvQRQjhB1FvA/kxXNubCjAOPLUz1j+BEkUUgwi65l8+pViHUCIhBdLJn7S0J9HbMsc3jXOFusfvXfyvf2SZMqyhEscs/2HzYgbCWVSxQ//6eimZqvW4an1JIEfH1xUH3zS0HvS7NWChtX1Cj+jJ2vw+lyom6a84wJ2mjSRVcaV6JZa2mIf2IuXfX8EMZwKMDJagkQ7effV3MrnCNrXUXJYn8wVm6l+uP+R9mCn9qbGlvWToCID0YM8MTxJ0LgzC0n93m46OxxCI1p1hPzVrmDCcKt3OASe0zVat415/sz9TTU7Rm8oNSAVUVNo7FRg2XwEb9VupGMjlscu4cJKLCtUpImSKDKlVLH2KYy6s4NdPjOjqtjQLRoaCxbYHmNXXE+vVwPiNBK/bBVT3XJgdmbkXa/PDnMjhAmn0huuxBu8GsTOs+bBdyDCbr6mY6EzN9lCPspEoOPs7nmREpjrvGmEqEddRteyAebpVqZQhqklyT3RVl8AmfoF7QgTi0NYqFb50Lgou3hFrMg7isG5EKU+IKI5WfzyWONlHNSpvO',
+ 'hash': 'qyi5vQ2HCVjVMHb+l6HQ4krmvwwEkvm6Qmdf5GJcohM=',
+ 'secret': 'kgJE0VLB9enOq5fhhX8gOv0xoaMIcskmRPOG1eMbiC/Q8slr7kur12H/YIoOfd7/DQ0ggE7TAAe34PypFvtmwt5fDVtqtPl9YoCeAOCFWxHxLgTCLbzoJ0lTJXoJkdHjlvR2lKaP+rMtaU1w8WOpYOGiNXyblQoWwFRrWNTHmHnmwBfGBFCj/vp89+C1viEYHeWPPUkBhf1vT31L70BEoe8hxORJEDg+jY+80W2nFdIWNBF+o9GSmbMWFtd7UFiuLPp2JUBCy8XuHozk8xFk/PN6m6DgSu32rC4YBJv/sWGUo/MmH0nxR3gaiEkj+9rWIybCNAwgfdQpk/KH2RCF8g=='}}
+
+
+@pytest.fixture(scope='function')
+def all_passport_data():
+ return [{'type': 'personal_details',
+ 'data': RAW_PASSPORT_DATA['data'][0]['data']},
+ {'type': 'passport',
+ 'data': RAW_PASSPORT_DATA['data'][1]['data'],
+ 'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
+ 'selfie': RAW_PASSPORT_DATA['data'][1]['selfie']},
+ {'type': 'internal_passport',
+ 'data': RAW_PASSPORT_DATA['data'][1]['data'],
+ 'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
+ 'selfie': RAW_PASSPORT_DATA['data'][1]['selfie']},
+ {'type': 'driver_license',
+ 'data': RAW_PASSPORT_DATA['data'][1]['data'],
+ 'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
+ 'reverse_side': RAW_PASSPORT_DATA['data'][1]['reverse_side'],
+ 'selfie': RAW_PASSPORT_DATA['data'][1]['selfie']},
+ {'type': 'identity_card',
+ 'data': RAW_PASSPORT_DATA['data'][1]['data'],
+ 'front_side': RAW_PASSPORT_DATA['data'][1]['front_side'],
+ 'reverse_side': RAW_PASSPORT_DATA['data'][1]['reverse_side'],
+ 'selfie': RAW_PASSPORT_DATA['data'][1]['selfie']},
+ {'type': 'address',
+ 'data': RAW_PASSPORT_DATA['data'][2]['data']},
+ {'type': 'utility_bill',
+ 'files': RAW_PASSPORT_DATA['data'][3]['files']},
+ {'type': 'bank_statement',
+ 'files': RAW_PASSPORT_DATA['data'][3]['files']},
+ {'type': 'rental_agreement',
+ 'files': RAW_PASSPORT_DATA['data'][3]['files']},
+ {'type': 'passport_registration',
+ 'files': RAW_PASSPORT_DATA['data'][3]['files']},
+ {'type': 'temporary_registration',
+ 'files': RAW_PASSPORT_DATA['data'][3]['files']},
+ {'type': 'email',
+ 'email': 'fb3e3i47zt@dispostable.com'},
+ {'type': 'phone_number',
+ 'phone_number': 'fb3e3i47zt@dispostable.com'}]
+
+
+@pytest.fixture(scope='function')
+def passport_data(bot):
+ return PassportData.de_json(RAW_PASSPORT_DATA, bot=bot)
+
+
+class TestPassport(object):
+ driver_license_selfie_file_id = 'DgADBAADEQQAAkopgFNr6oi-wISRtAI'
+ driver_license_front_side_file_id = 'DgADBAADxwMAApnQgVPK2-ckL2eXVAI'
+ driver_license_reverse_side_file_id = 'DgADBAADNQQAAtoagFPf4wwmFZdmyQI'
+ utility_bill_1_file_id = 'DgADBAADLAMAAhwfgVMyfGa5Nr0LvAI'
+ utility_bill_2_file_id = 'DgADBAADaQQAAsFxgVNVfLZuT-_3ZQI'
+ driver_license_selfie_credentials_file_hash = 'Cila/qLXSBH7DpZFbb5bRZIRxeFW2uv/ulL0u0JNsYI='
+ driver_license_selfie_credentials_secret = 'tivdId6RNYNsvXYPppdzrbxOBuBOr9wXRPDcCvnXU7E='
+
+ def test_creation(self, passport_data):
+ assert isinstance(passport_data, PassportData)
+
+ def test_expected_encrypted_values(self, passport_data):
+ personal_details, driver_license, address, utility_bill, email = passport_data.data
+
+ assert personal_details.type == 'personal_details'
+ assert personal_details.data == RAW_PASSPORT_DATA['data'][0]['data']
+
+ assert driver_license.type == 'driver_license'
+ assert driver_license.data == RAW_PASSPORT_DATA['data'][1]['data']
+ assert isinstance(driver_license.selfie, PassportFile)
+ assert driver_license.selfie.file_id == self.driver_license_selfie_file_id
+ assert isinstance(driver_license.front_side, PassportFile)
+ assert driver_license.front_side.file_id == self.driver_license_front_side_file_id
+ assert isinstance(driver_license.reverse_side, PassportFile)
+ assert driver_license.reverse_side.file_id == self.driver_license_reverse_side_file_id
+
+ assert address.type == 'address'
+ assert address.data == RAW_PASSPORT_DATA['data'][2]['data']
+
+ assert utility_bill.type == 'utility_bill'
+ assert isinstance(utility_bill.files[0], PassportFile)
+ assert utility_bill.files[0].file_id == self.utility_bill_1_file_id
+ assert isinstance(utility_bill.files[1], PassportFile)
+ assert utility_bill.files[1].file_id == self.utility_bill_2_file_id
+
+ assert email.type == 'email'
+ assert email.email == 'fb3e3i47zt@dispostable.com'
+
+ def test_expected_decrypted_values(self, passport_data):
+ (personal_details, driver_license, address,
+ utility_bill, email) = passport_data.decrypted_data
+
+ assert personal_details.type == 'personal_details'
+ assert personal_details.data.to_dict() == {'gender': 'female',
+ 'residence_country_code': 'DK',
+ 'country_code': 'DK',
+ 'birth_date': '01.01.2001',
+ 'first_name': 'FIRSTNAME',
+ 'last_name': 'LASTNAME'}
+
+ assert driver_license.type == 'driver_license'
+ assert driver_license.data.to_dict() == {'expiry_date': '01.01.2001',
+ 'document_no': 'DOCUMENT_NO'}
+ assert isinstance(driver_license.selfie, PassportFile)
+ assert driver_license.selfie.file_id == self.driver_license_selfie_file_id
+ assert isinstance(driver_license.front_side, PassportFile)
+ assert driver_license.front_side.file_id == self.driver_license_front_side_file_id
+ assert isinstance(driver_license.reverse_side, PassportFile)
+ assert driver_license.reverse_side.file_id == self.driver_license_reverse_side_file_id
+
+ assert address.type == 'address'
+ assert address.data.to_dict() == {'city': 'CITY', 'street_line2': 'STREET_LINE2',
+ 'state': 'STATE', 'post_code': 'POSTCODE',
+ 'country_code': 'DK', 'street_line1': 'STREET_LINE1'}
+
+ assert utility_bill.type == 'utility_bill'
+ assert isinstance(utility_bill.files[0], PassportFile)
+ assert utility_bill.files[0].file_id == self.utility_bill_1_file_id
+ assert isinstance(utility_bill.files[1], PassportFile)
+ assert utility_bill.files[1].file_id == self.utility_bill_2_file_id
+
+ assert email.type == 'email'
+ assert email.email == 'fb3e3i47zt@dispostable.com'
+
+ def test_all_types(self, passport_data, bot, all_passport_data):
+ credentials = passport_data.decrypted_credentials.to_dict()
+
+ # Copy credentials from other types to all types so we can decrypt everything
+ sd = credentials['secure_data']
+ credentials['secure_data'] = {
+ 'personal_details': sd['personal_details'].copy(),
+ 'passport': sd['driver_license'].copy(),
+ 'internal_passport': sd['driver_license'].copy(),
+ 'driver_license': sd['driver_license'].copy(),
+ 'identity_card': sd['driver_license'].copy(),
+ 'address': sd['address'].copy(),
+ 'utility_bill': sd['utility_bill'].copy(),
+ 'bank_statement': sd['utility_bill'].copy(),
+ 'rental_agreement': sd['utility_bill'].copy(),
+ 'passport_registration': sd['utility_bill'].copy(),
+ 'temporary_registration': sd['utility_bill'].copy(),
+ }
+
+ new = PassportData.de_json({
+ 'data': all_passport_data,
+ # Replaced below
+ 'credentials': {'data': 'data', 'hash': 'hash', 'secret': 'secret'}
+ }, bot=bot)
+
+ new.credentials._decrypted_data = Credentials.de_json(credentials, bot)
+
+ assert isinstance(new, PassportData)
+ assert new.decrypted_data
+
+ def test_bot_init_invalid_key(self, bot):
+ with pytest.raises(TypeError):
+ Bot(bot.token, private_key=u'Invalid key!')
+
+ with pytest.raises(ValueError):
+ Bot(bot.token, private_key=b'Invalid key!')
+
+ def test_passport_data_okay_with_non_crypto_bot(self, bot):
+ b = Bot(bot.token)
+ assert PassportData.de_json(RAW_PASSPORT_DATA, bot=b)
+
+ def test_wrong_hash(self, bot):
+ data = deepcopy(RAW_PASSPORT_DATA)
+ data['credentials']['hash'] = b'notcorrecthash'
+ passport_data = PassportData.de_json(data, bot=bot)
+ with pytest.raises(TelegramDecryptionError):
+ assert passport_data.decrypted_data
+
+ def test_wrong_key(self, bot):
+ short_key = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIBOQIBAAJBAKU+OZ2jJm7sCA/ec4gngNZhXYPu+DZ/TAwSMl0W7vAPXAsLplBk\r\nO8l6IBHx8N0ZC4Bc65mO3b2G8YAzqndyqH8CAwEAAQJAWOx3jQFzeVXDsOaBPdAk\r\nYTncXVeIc6tlfUl9mOLyinSbRNCy1XicOiOZFgH1rRKOGIC1235QmqxFvdecySoY\r\nwQIhAOFeGgeX9CrEPuSsd9+kqUcA2avCwqdQgSdy2qggRFyJAiEAu7QHT8JQSkHU\r\nDELfzrzc24AhjyG0z1DpGZArM8COascCIDK42SboXj3Z2UXiQ0CEcMzYNiVgOisq\r\nBUd5pBi+2mPxAiAM5Z7G/Sv1HjbKrOGh29o0/sXPhtpckEuj5QMC6E0gywIgFY6S\r\nNjwrAA+cMmsgY0O2fAzEKkDc5YiFsiXaGaSS4eA=\r\n-----END RSA PRIVATE KEY-----"
+ b = Bot(bot.token, private_key=short_key)
+ passport_data = PassportData.de_json(RAW_PASSPORT_DATA, bot=b)
+ with pytest.raises(TelegramDecryptionError):
+ assert passport_data.decrypted_data
+
+ wrong_key = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIEogIBAAKCAQB4qCFltuvHakZze86TUweU7E/SB3VLGEHAe7GJlBmrou9SSWsL\r\nH7E++157X6UqWFl54LOE9MeHZnoW7rZ+DxLKhk6NwAHTxXPnvw4CZlvUPC3OFxg3\r\nhEmNen6ojSM4sl4kYUIa7F+Q5uMEYaboxoBen9mbj4zzMGsG4aY/xBOb2ewrXQyL\r\nRh//tk1Px4ago+lUPisAvQVecz7/6KU4Xj4Lpv2z20f3cHlZX6bb7HlE1vixCMOf\r\nxvfC5SkWEGZMR/ZoWQUsoDkrDSITF/S3GtLfg083TgtCKaOF3mCT27sJ1og77npP\r\n0cH/qdlbdoFtdrRj3PvBpaj/TtXRhmdGcJBxAgMBAAECggEAYSq1Sp6XHo8dkV8B\r\nK2/QSURNu8y5zvIH8aUrgqo8Shb7OH9bryekrB3vJtgNwR5JYHdu2wHttcL3S4SO\r\nftJQxbyHgmxAjHUVNGqOM6yPA0o7cR70J7FnMoKVgdO3q68pVY7ll50IET9/T0X9\r\nDrTdKFb+/eILFsXFS1NpeSzExdsKq3zM0sP/vlJHHYVTmZDGaGEvny/eLAS+KAfG\r\nrKP96DeO4C/peXEJzALZ/mG1ReBB05Qp9Dx1xEC20yreRk5MnnBA5oiHVG5ZLOl9\r\nEEHINidqN+TMNSkxv67xMfQ6utNu5IpbklKv/4wqQOJOO50HZ+qBtSurTN573dky\r\nzslbCQKBgQDHDUBYyKN/v69VLmvNVcxTgrOcrdbqAfefJXb9C3dVXhS8/oRkCRU/\r\ndzxYWNT7hmQyWUKor/izh68rZ/M+bsTnlaa7IdAgyChzTfcZL/2pxG9pq05GF1Q4\r\nBSJ896ZEe3jEhbpJXRlWYvz7455svlxR0H8FooCTddTmkU3nsQSx0wKBgQCbLSa4\r\nyZs2QVstQQerNjxAtLi0IvV8cJkuvFoNC2Q21oqQc7BYU7NJL7uwriprZr5nwkCQ\r\nOFQXi4N3uqimNxuSng31ETfjFZPp+pjb8jf7Sce7cqU66xxR+anUzVZqBG1CJShx\r\nVxN7cWN33UZvIH34gA2Ax6AXNnJG42B5Gn1GKwKBgQCZ/oh/p4nGNXfiAK3qB6yy\r\nFvX6CwuvsqHt/8AUeKBz7PtCU+38roI/vXF0MBVmGky+HwxREQLpcdl1TVCERpIT\r\nUFXThI9OLUwOGI1IcTZf9tby+1LtKvM++8n4wGdjp9qAv6ylQV9u09pAzZItMwCd\r\nUx5SL6wlaQ2y60tIKk0lfQKBgBJS+56YmA6JGzY11qz+I5FUhfcnpauDNGOTdGLT\r\n9IqRPR2fu7RCdgpva4+KkZHLOTLReoRNUojRPb4WubGfEk93AJju5pWXR7c6k3Bt\r\novS2mrJk8GQLvXVksQxjDxBH44sLDkKMEM3j7uYJqDaZNKbyoCWT7TCwikAau5qx\r\naRevAoGAAKZV705dvrpJuyoHFZ66luANlrAwG/vNf6Q4mBEXB7guqMkokCsSkjqR\r\nhsD79E6q06zA0QzkLCavbCn5kMmDS/AbA80+B7El92iIN6d3jRdiNZiewkhlWhEG\r\nm4N0gQRfIu+rUjsS/4xk8UuQUT/Ossjn/hExi7ejpKdCc7N++bc=\r\n-----END RSA PRIVATE KEY-----"
+ b = Bot(bot.token, private_key=wrong_key)
+ passport_data = PassportData.de_json(RAW_PASSPORT_DATA, bot=b)
+ with pytest.raises(TelegramDecryptionError):
+ assert passport_data.decrypted_data
+
+ def test_mocked_download_passport_file(self, passport_data, monkeypatch):
+ # The files are not coming from our test bot, therefore the file id is invalid/wrong
+ # when coming from this bot, so we monkeypatch the call, to make sure that Bot.get_file
+ # at least gets called
+ # TODO: Actually download a passport file in a test
+ selfie = passport_data.decrypted_data[1].selfie
+
+ def get_file(*args, **kwargs):
+ return File(args[1])
+
+ monkeypatch.setattr('telegram.Bot.get_file', get_file)
+ file = selfie.get_file()
+ assert file.file_id == selfie.file_id
+ assert file._credentials.file_hash == self.driver_license_selfie_credentials_file_hash
+ assert file._credentials.secret == self.driver_license_selfie_credentials_secret
+
+ def test_mocked_set_passport_data_errors(self, monkeypatch, bot, chat_id, passport_data):
+ def test(_, url, data, **kwargs):
+ return (data['user_id'] == chat_id and
+ data['errors'][0]['file_hash'] == (passport_data.decrypted_credentials
+ .secure_data.driver_license
+ .selfie.file_hash) and
+ data['errors'][1]['data_hash'] == (passport_data.decrypted_credentials
+ .secure_data.driver_license
+ .data.data_hash))
+
+ monkeypatch.setattr('telegram.utils.request.Request.post', test)
+ message = bot.set_passport_data_errors(chat_id, [
+ PassportElementErrorSelfie('driver_license',
+ (passport_data.decrypted_credentials
+ .secure_data.driver_license.selfie.file_hash),
+ 'You\'re not handsome enough to use this app!'),
+ PassportElementErrorDataField('driver_license',
+ 'expiry_date',
+ (passport_data.decrypted_credentials
+ .secure_data.driver_license.data.data_hash),
+ 'Your driver license is expired!')
+ ])
+ assert message
+
+ def test_de_json_and_to_dict(self, bot):
+ passport_data = PassportData.de_json(RAW_PASSPORT_DATA, bot)
+ assert passport_data.to_dict() == RAW_PASSPORT_DATA
+
+ assert passport_data.decrypted_data
+ assert passport_data.to_dict() == RAW_PASSPORT_DATA
+
+ def test_equality(self, passport_data):
+ a = PassportData(passport_data.data, passport_data.credentials)
+ b = PassportData(passport_data.data, passport_data.credentials)
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ passport_data.credentials.hash = 'NOTAPROPERHASH'
+ c = PassportData(passport_data.data, passport_data.credentials)
+
+ assert a != c
+ assert hash(a) != hash(c)
diff --git a/tests/test_passportelementerrordatafield.py b/tests/test_passportelementerrordatafield.py
new file mode 100644
index 000000000..0a286b321
--- /dev/null
+++ b/tests/test_passportelementerrordatafield.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 PassportElementErrorDataField, PassportElementErrorSelfie
+
+
+@pytest.fixture(scope='class')
+def passport_element_error_data_field():
+ return PassportElementErrorDataField(TestPassportElementErrorDataField.type,
+ TestPassportElementErrorDataField.field_name,
+ TestPassportElementErrorDataField.data_hash,
+ TestPassportElementErrorDataField.message)
+
+
+class TestPassportElementErrorDataField(object):
+ source = 'data'
+ type = 'test_type'
+ field_name = 'test_field'
+ data_hash = 'data_hash'
+ message = 'Error message'
+
+ def test_expected_values(self, passport_element_error_data_field):
+ assert passport_element_error_data_field.source == self.source
+ assert passport_element_error_data_field.type == self.type
+ assert passport_element_error_data_field.field_name == self.field_name
+ assert passport_element_error_data_field.data_hash == self.data_hash
+ assert passport_element_error_data_field.message == self.message
+
+ def test_to_dict(self, passport_element_error_data_field):
+ passport_element_error_data_field_dict = passport_element_error_data_field.to_dict()
+
+ assert isinstance(passport_element_error_data_field_dict, dict)
+ assert (passport_element_error_data_field_dict['source'] ==
+ passport_element_error_data_field.source)
+ assert (passport_element_error_data_field_dict['type'] ==
+ passport_element_error_data_field.type)
+ assert (passport_element_error_data_field_dict['field_name'] ==
+ passport_element_error_data_field.field_name)
+ assert (passport_element_error_data_field_dict['data_hash'] ==
+ passport_element_error_data_field.data_hash)
+ assert (passport_element_error_data_field_dict['message'] ==
+ passport_element_error_data_field.message)
+
+ def test_equality(self):
+ a = PassportElementErrorDataField(self.type, self.field_name, self.data_hash, self.message)
+ b = PassportElementErrorDataField(self.type, self.field_name, self.data_hash, self.message)
+ c = PassportElementErrorDataField(self.type, '', '', '')
+ d = PassportElementErrorDataField('', self.field_name, '', '')
+ e = PassportElementErrorDataField('', '', self.data_hash, '')
+ f = PassportElementErrorDataField('', '', '', self.message)
+ g = PassportElementErrorSelfie(self.type, '', self.message)
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ assert a != c
+ assert hash(a) != hash(c)
+
+ assert a != d
+ assert hash(a) != hash(d)
+
+ assert a != e
+ assert hash(a) != hash(e)
+
+ assert a != f
+ assert hash(a) != hash(f)
+
+ assert a != g
+ assert hash(a) != hash(g)
diff --git a/tests/test_passportelementerrorfile.py b/tests/test_passportelementerrorfile.py
new file mode 100644
index 000000000..01e58b7fa
--- /dev/null
+++ b/tests/test_passportelementerrorfile.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 PassportElementErrorFile, PassportElementErrorSelfie
+
+
+@pytest.fixture(scope='class')
+def passport_element_error_file():
+ return PassportElementErrorFile(TestPassportElementErrorFile.type,
+ TestPassportElementErrorFile.file_hash,
+ TestPassportElementErrorFile.message)
+
+
+class TestPassportElementErrorFile(object):
+ source = 'file'
+ type = 'test_type'
+ file_hash = 'file_hash'
+ message = 'Error message'
+
+ def test_expected_values(self, passport_element_error_file):
+ assert passport_element_error_file.source == self.source
+ assert passport_element_error_file.type == self.type
+ assert passport_element_error_file.file_hash == self.file_hash
+ assert passport_element_error_file.message == self.message
+
+ def test_to_dict(self, passport_element_error_file):
+ passport_element_error_file_dict = passport_element_error_file.to_dict()
+
+ assert isinstance(passport_element_error_file_dict, dict)
+ assert (passport_element_error_file_dict['source'] ==
+ passport_element_error_file.source)
+ assert (passport_element_error_file_dict['type'] ==
+ passport_element_error_file.type)
+ assert (passport_element_error_file_dict['file_hash'] ==
+ passport_element_error_file.file_hash)
+ assert (passport_element_error_file_dict['message'] ==
+ passport_element_error_file.message)
+
+ def test_equality(self):
+ a = PassportElementErrorFile(self.type, self.file_hash, self.message)
+ b = PassportElementErrorFile(self.type, self.file_hash, self.message)
+ c = PassportElementErrorFile(self.type, '', '')
+ d = PassportElementErrorFile('', self.file_hash, '')
+ e = PassportElementErrorFile('', '', self.message)
+ f = PassportElementErrorSelfie(self.type, self.file_hash, self.message)
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ assert a != c
+ assert hash(a) != hash(c)
+
+ assert a != d
+ assert hash(a) != hash(d)
+
+ assert a != e
+ assert hash(a) != hash(e)
+
+ assert a != f
+ assert hash(a) != hash(f)
diff --git a/tests/test_passportelementerrorfiles.py b/tests/test_passportelementerrorfiles.py
new file mode 100644
index 000000000..49cbd3e12
--- /dev/null
+++ b/tests/test_passportelementerrorfiles.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 PassportElementErrorFiles, PassportElementErrorSelfie
+
+
+@pytest.fixture(scope='class')
+def passport_element_error_files():
+ return PassportElementErrorFiles(TestPassportElementErrorFiles.type,
+ TestPassportElementErrorFiles.file_hashes,
+ TestPassportElementErrorFiles.message)
+
+
+class TestPassportElementErrorFiles(object):
+ source = 'files'
+ type = 'test_type'
+ file_hashes = ['hash1', 'hash2']
+ message = 'Error message'
+
+ def test_expected_values(self, passport_element_error_files):
+ assert passport_element_error_files.source == self.source
+ assert passport_element_error_files.type == self.type
+ assert isinstance(passport_element_error_files.file_hashes, list)
+ assert passport_element_error_files.file_hashes == self.file_hashes
+ assert passport_element_error_files.message == self.message
+
+ def test_to_dict(self, passport_element_error_files):
+ passport_element_error_files_dict = passport_element_error_files.to_dict()
+
+ assert isinstance(passport_element_error_files_dict, dict)
+ assert (passport_element_error_files_dict['source'] ==
+ passport_element_error_files.source)
+ assert (passport_element_error_files_dict['type'] ==
+ passport_element_error_files.type)
+ assert (passport_element_error_files_dict['file_hashes'] ==
+ passport_element_error_files.file_hashes)
+ assert (passport_element_error_files_dict['message'] ==
+ passport_element_error_files.message)
+
+ def test_equality(self):
+ a = PassportElementErrorFiles(self.type, self.file_hashes, self.message)
+ b = PassportElementErrorFiles(self.type, self.file_hashes, self.message)
+ c = PassportElementErrorFiles(self.type, '', '')
+ d = PassportElementErrorFiles('', self.file_hashes, '')
+ e = PassportElementErrorFiles('', '', self.message)
+ f = PassportElementErrorSelfie(self.type, '', self.message)
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ assert a != c
+ assert hash(a) != hash(c)
+
+ assert a != d
+ assert hash(a) != hash(d)
+
+ assert a != e
+ assert hash(a) != hash(e)
+
+ assert a != f
+ assert hash(a) != hash(f)
diff --git a/tests/test_passportelementerrorfrontside.py b/tests/test_passportelementerrorfrontside.py
new file mode 100644
index 000000000..f9a2752bb
--- /dev/null
+++ b/tests/test_passportelementerrorfrontside.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 PassportElementErrorFrontSide, PassportElementErrorSelfie
+
+
+@pytest.fixture(scope='class')
+def passport_element_error_front_side():
+ return PassportElementErrorFrontSide(TestPassportElementErrorFrontSide.type,
+ TestPassportElementErrorFrontSide.file_hash,
+ TestPassportElementErrorFrontSide.message)
+
+
+class TestPassportElementErrorFrontSide(object):
+ source = 'front_side'
+ type = 'test_type'
+ file_hash = 'file_hash'
+ message = 'Error message'
+
+ def test_expected_values(self, passport_element_error_front_side):
+ assert passport_element_error_front_side.source == self.source
+ assert passport_element_error_front_side.type == self.type
+ assert passport_element_error_front_side.file_hash == self.file_hash
+ assert passport_element_error_front_side.message == self.message
+
+ def test_to_dict(self, passport_element_error_front_side):
+ passport_element_error_front_side_dict = passport_element_error_front_side.to_dict()
+
+ assert isinstance(passport_element_error_front_side_dict, dict)
+ assert (passport_element_error_front_side_dict['source'] ==
+ passport_element_error_front_side.source)
+ assert (passport_element_error_front_side_dict['type'] ==
+ passport_element_error_front_side.type)
+ assert (passport_element_error_front_side_dict['file_hash'] ==
+ passport_element_error_front_side.file_hash)
+ assert (passport_element_error_front_side_dict['message'] ==
+ passport_element_error_front_side.message)
+
+ def test_equality(self):
+ a = PassportElementErrorFrontSide(self.type, self.file_hash, self.message)
+ b = PassportElementErrorFrontSide(self.type, self.file_hash, self.message)
+ c = PassportElementErrorFrontSide(self.type, '', '')
+ d = PassportElementErrorFrontSide('', self.file_hash, '')
+ e = PassportElementErrorFrontSide('', '', self.message)
+ f = PassportElementErrorSelfie(self.type, self.file_hash, self.message)
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ assert a != c
+ assert hash(a) != hash(c)
+
+ assert a != d
+ assert hash(a) != hash(d)
+
+ assert a != e
+ assert hash(a) != hash(e)
+
+ assert a != f
+ assert hash(a) != hash(f)
diff --git a/tests/test_passportelementerrorreverseside.py b/tests/test_passportelementerrorreverseside.py
new file mode 100644
index 000000000..37bcb8acc
--- /dev/null
+++ b/tests/test_passportelementerrorreverseside.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 PassportElementErrorReverseSide, PassportElementErrorSelfie
+
+
+@pytest.fixture(scope='class')
+def passport_element_error_reverse_side():
+ return PassportElementErrorReverseSide(TestPassportElementErrorReverseSide.type,
+ TestPassportElementErrorReverseSide.file_hash,
+ TestPassportElementErrorReverseSide.message)
+
+
+class TestPassportElementErrorReverseSide(object):
+ source = 'reverse_side'
+ type = 'test_type'
+ file_hash = 'file_hash'
+ message = 'Error message'
+
+ def test_expected_values(self, passport_element_error_reverse_side):
+ assert passport_element_error_reverse_side.source == self.source
+ assert passport_element_error_reverse_side.type == self.type
+ assert passport_element_error_reverse_side.file_hash == self.file_hash
+ assert passport_element_error_reverse_side.message == self.message
+
+ def test_to_dict(self, passport_element_error_reverse_side):
+ passport_element_error_reverse_side_dict = passport_element_error_reverse_side.to_dict()
+
+ assert isinstance(passport_element_error_reverse_side_dict, dict)
+ assert (passport_element_error_reverse_side_dict['source'] ==
+ passport_element_error_reverse_side.source)
+ assert (passport_element_error_reverse_side_dict['type'] ==
+ passport_element_error_reverse_side.type)
+ assert (passport_element_error_reverse_side_dict['file_hash'] ==
+ passport_element_error_reverse_side.file_hash)
+ assert (passport_element_error_reverse_side_dict['message'] ==
+ passport_element_error_reverse_side.message)
+
+ def test_equality(self):
+ a = PassportElementErrorReverseSide(self.type, self.file_hash, self.message)
+ b = PassportElementErrorReverseSide(self.type, self.file_hash, self.message)
+ c = PassportElementErrorReverseSide(self.type, '', '')
+ d = PassportElementErrorReverseSide('', self.file_hash, '')
+ e = PassportElementErrorReverseSide('', '', self.message)
+ f = PassportElementErrorSelfie(self.type, self.file_hash, self.message)
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ assert a != c
+ assert hash(a) != hash(c)
+
+ assert a != d
+ assert hash(a) != hash(d)
+
+ assert a != e
+ assert hash(a) != hash(e)
+
+ assert a != f
+ assert hash(a) != hash(f)
diff --git a/tests/test_passportelementerrorselfie.py b/tests/test_passportelementerrorselfie.py
new file mode 100644
index 000000000..f38924de3
--- /dev/null
+++ b/tests/test_passportelementerrorselfie.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 PassportElementErrorSelfie, PassportElementErrorDataField
+
+
+@pytest.fixture(scope='class')
+def passport_element_error_selfie():
+ return PassportElementErrorSelfie(TestPassportElementErrorSelfie.type,
+ TestPassportElementErrorSelfie.file_hash,
+ TestPassportElementErrorSelfie.message)
+
+
+class TestPassportElementErrorSelfie(object):
+ source = 'selfie'
+ type = 'test_type'
+ file_hash = 'file_hash'
+ message = 'Error message'
+
+ def test_expected_values(self, passport_element_error_selfie):
+ assert passport_element_error_selfie.source == self.source
+ assert passport_element_error_selfie.type == self.type
+ assert passport_element_error_selfie.file_hash == self.file_hash
+ assert passport_element_error_selfie.message == self.message
+
+ def test_to_dict(self, passport_element_error_selfie):
+ passport_element_error_selfie_dict = passport_element_error_selfie.to_dict()
+
+ assert isinstance(passport_element_error_selfie_dict, dict)
+ assert (passport_element_error_selfie_dict['source'] ==
+ passport_element_error_selfie.source)
+ assert (passport_element_error_selfie_dict['type'] ==
+ passport_element_error_selfie.type)
+ assert (passport_element_error_selfie_dict['file_hash'] ==
+ passport_element_error_selfie.file_hash)
+ assert (passport_element_error_selfie_dict['message'] ==
+ passport_element_error_selfie.message)
+
+ def test_equality(self):
+ a = PassportElementErrorSelfie(self.type, self.file_hash, self.message)
+ b = PassportElementErrorSelfie(self.type, self.file_hash, self.message)
+ c = PassportElementErrorSelfie(self.type, '', '')
+ d = PassportElementErrorSelfie('', self.file_hash, '')
+ e = PassportElementErrorSelfie('', '', self.message)
+ f = PassportElementErrorDataField(self.type, '', '', self.message)
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ assert a != c
+ assert hash(a) != hash(c)
+
+ assert a != d
+ assert hash(a) != hash(d)
+
+ assert a != e
+ assert hash(a) != hash(e)
+
+ assert a != f
+ assert hash(a) != hash(f)
diff --git a/tests/test_passportfile.py b/tests/test_passportfile.py
new file mode 100644
index 000000000..da9c5ae2d
--- /dev/null
+++ b/tests/test_passportfile.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+#
+# A library that provides a Python interface to the Telegram Bot API
+# Copyright (C) 2015-2018
+# Leandro Toledo de Souza
+#
+# 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 PassportFile, PassportElementError
+
+
+@pytest.fixture(scope='class')
+def passport_file():
+ return PassportFile(file_id=TestPassportFile.file_id,
+ file_size=TestPassportFile.file_size,
+ file_date=TestPassportFile.file_date)
+
+
+class TestPassportFile(object):
+ file_id = 'data'
+ file_size = 50
+ file_date = 1532879128
+
+ def test_expected_values(self, passport_file):
+ assert passport_file.file_id == self.file_id
+ assert passport_file.file_size == self.file_size
+ assert passport_file.file_date == self.file_date
+
+ def test_to_dict(self, passport_file):
+ passport_file_dict = passport_file.to_dict()
+
+ assert isinstance(passport_file_dict, dict)
+ assert (passport_file_dict['file_id'] ==
+ passport_file.file_id)
+ assert (passport_file_dict['file_size'] ==
+ passport_file.file_size)
+ assert (passport_file_dict['file_date'] ==
+ passport_file.file_date)
+
+ def test_equality(self):
+ a = PassportFile(self.file_id, self.file_size, self.file_date)
+ b = PassportFile(self.file_id, self.file_size, self.file_date)
+ c = PassportFile(self.file_id, '', '')
+ d = PassportFile('', self.file_size, self.file_date)
+ e = PassportElementError('source', 'type', 'message')
+
+ assert a == b
+ assert hash(a) == hash(b)
+ assert a is not b
+
+ assert a == c
+ assert hash(a) == hash(c)
+
+ assert a != d
+ assert hash(a) != hash(d)
+
+ assert a != e
+ assert hash(a) != hash(e)
diff --git a/tests/test_photo.py b/tests/test_photo.py
index 8647b0935..b3f71d22e 100644
--- a/tests/test_photo.py
+++ b/tests/test_photo.py
@@ -202,14 +202,14 @@ class TestPhoto(object):
# raw image bytes
raw_bytes = BytesIO(open(file_name, 'rb').read())
- inputfile = InputFile({'photo': raw_bytes})
- assert inputfile.mimetype == 'application/octet-stream'
+ input_file = InputFile(raw_bytes)
+ assert input_file.mimetype == 'application/octet-stream'
# raw image bytes with name info
raw_bytes = BytesIO(open(file_name, 'rb').read())
raw_bytes.name = file_name
- inputfile = InputFile({'photo': raw_bytes})
- assert inputfile.mimetype == 'image/jpeg'
+ input_file = InputFile(raw_bytes)
+ assert input_file.mimetype == 'image/jpeg'
# send raw photo
raw_bytes = BytesIO(open(file_name, 'rb').read())
diff --git a/tests/test_updater.py b/tests/test_updater.py
index 85c4a6c99..225862c5a 100644
--- a/tests/test_updater.py
+++ b/tests/test_updater.py
@@ -46,14 +46,6 @@ signalskip = pytest.mark.skipif(sys.platform == 'win32',
'whole process on windows')
-@pytest.fixture(scope='function')
-def updater(bot):
- up = Updater(bot=bot, workers=2)
- yield up
- if up.running:
- up.stop()
-
-
class TestUpdater(object):
message_count = 0
received = None
@@ -359,3 +351,8 @@ class TestUpdater(object):
def test_no_token_or_bot(self):
with pytest.raises(ValueError):
Updater()
+
+ def test_mutual_exclude_bot_private_key(self):
+ bot = Bot('123:zyxw')
+ with pytest.raises(ValueError):
+ Updater(bot=bot, private_key=b'key')
diff --git a/tests/test_user.py b/tests/test_user.py
index 8d1eced53..cdcdea50f 100644
--- a/tests/test_user.py
+++ b/tests/test_user.py
@@ -165,6 +165,13 @@ class TestUser(object):
monkeypatch.setattr('telegram.Bot.send_voice', test)
assert user.send_voice('test_voice')
+ def test_instance_method_send_animation(self, monkeypatch, user):
+ def test(*args, **kwargs):
+ return args[1] == user.id and args[2] == 'test_animation'
+
+ monkeypatch.setattr('telegram.Bot.send_animation', test)
+ assert user.send_animation('test_animation')
+
def test_mention_html(self, user):
expected = u'{}'
diff --git a/tests/test_venue.py b/tests/test_venue.py
index 226b819fd..35d16a0ba 100644
--- a/tests/test_venue.py
+++ b/tests/test_venue.py
@@ -27,7 +27,8 @@ def venue():
return Venue(TestVenue.location,
TestVenue.title,
TestVenue.address,
- foursquare_id=TestVenue.foursquare_id)
+ foursquare_id=TestVenue.foursquare_id,
+ foursquare_type=TestVenue.foursquare_type)
class TestVenue(object):
@@ -35,13 +36,15 @@ class TestVenue(object):
title = 'title'
address = 'address'
foursquare_id = 'foursquare id'
+ foursquare_type = 'foursquare type'
def test_de_json(self, bot):
json_dict = {
'location': TestVenue.location.to_dict(),
'title': TestVenue.title,
'address': TestVenue.address,
- 'foursquare_id': TestVenue.foursquare_id
+ 'foursquare_id': TestVenue.foursquare_id,
+ 'foursquare_type': TestVenue.foursquare_type
}
venue = Venue.de_json(json_dict, bot)
@@ -49,6 +52,7 @@ class TestVenue(object):
assert venue.title == self.title
assert venue.address == self.address
assert venue.foursquare_id == self.foursquare_id
+ assert venue.foursquare_type == self.foursquare_type
def test_send_with_venue(self, monkeypatch, bot, chat_id, venue):
def test(_, url, data, **kwargs):
@@ -56,7 +60,8 @@ class TestVenue(object):
and data['latitude'] == self.location.latitude
and data['title'] == self.title
and data['address'] == self.address
- and data['foursquare_id'] == self.foursquare_id)
+ and data['foursquare_id'] == self.foursquare_id
+ and data['foursquare_type'] == self.foursquare_type)
monkeypatch.setattr('telegram.utils.request.Request.post', test)
message = bot.send_venue(chat_id, venue=venue)
@@ -74,6 +79,7 @@ class TestVenue(object):
assert venue_dict['title'] == venue.title
assert venue_dict['address'] == venue.address
assert venue_dict['foursquare_id'] == venue.foursquare_id
+ assert venue_dict['foursquare_type'] == venue.foursquare_type
def test_equality(self):
a = Venue(Location(0, 0), self.title, self.address)
diff --git a/tests/test_video.py b/tests/test_video.py
index 013f2b073..d91434029 100644
--- a/tests/test_video.py
+++ b/tests/test_video.py
@@ -67,11 +67,11 @@ class TestVideo(object):
@flaky(3, 1)
@pytest.mark.timeout(10)
- def test_send_all_args(self, bot, chat_id, video_file, video):
+ def test_send_all_args(self, bot, chat_id, video_file, video, thumb_file):
message = bot.send_video(chat_id, video_file, duration=self.duration,
caption=self.caption, supports_streaming=self.supports_streaming,
disable_notification=False, width=video.width,
- height=video.height, parse_mode='Markdown')
+ height=video.height, parse_mode='Markdown', thumb=thumb_file)
assert isinstance(message.video, Video)
assert isinstance(message.video.file_id, str)
@@ -81,15 +81,11 @@ class TestVideo(object):
assert message.video.duration == video.duration
assert message.video.file_size == video.file_size
- assert isinstance(message.video.thumb, PhotoSize)
- assert isinstance(message.video.thumb.file_id, str)
- assert message.video.thumb.file_id != ''
- assert message.video.thumb.width == video.thumb.width
- assert message.video.thumb.height == video.thumb.height
- assert message.video.thumb.file_size == video.thumb.file_size
-
assert message.caption == self.caption.replace('*', '')
+ assert message.video.thumb.width == 50
+ assert message.video.thumb.height == 50
+
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_get_and_download(self, bot, video):
diff --git a/tests/test_videonote.py b/tests/test_videonote.py
index 16713dccb..2bc6dffb1 100644
--- a/tests/test_videonote.py
+++ b/tests/test_videonote.py
@@ -61,9 +61,10 @@ class TestVideoNote(object):
@flaky(3, 1)
@pytest.mark.timeout(10)
- def test_send_all_args(self, bot, chat_id, video_note_file, video_note):
+ def test_send_all_args(self, bot, chat_id, video_note_file, video_note, thumb_file):
message = bot.send_video_note(chat_id, video_note_file, duration=self.duration,
- length=self.length, disable_notification=False)
+ length=self.length, disable_notification=False,
+ thumb=thumb_file)
assert isinstance(message.video_note, VideoNote)
assert isinstance(message.video_note.file_id, str)
@@ -72,12 +73,8 @@ class TestVideoNote(object):
assert message.video_note.duration == video_note.duration
assert message.video_note.file_size == video_note.file_size
- assert isinstance(message.video_note.thumb, PhotoSize)
- assert isinstance(message.video_note.thumb.file_id, str)
- assert message.video_note.thumb.file_id != ''
- assert message.video_note.thumb.width == video_note.thumb.width
- assert message.video_note.thumb.height == video_note.thumb.height
- assert message.video_note.thumb.file_size == video_note.thumb.file_size
+ assert message.video_note.thumb.width == 50
+ assert message.video_note.thumb.height == 50
@flaky(3, 1)
@pytest.mark.timeout(10)