Bot API 4.0 (#1168)

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)


Co-authored-by: Jasmin Bom <jsmnbom@gmail.com>
Co-authored-by: code1mountain <32801117+code1mountain@users.noreply.github.com>
Co-authored-by: Eldinnie <pieter.schutz+github@gmail.com>
Co-authored-by: mathefreak1 <mathefreak@hi2.in>
This commit is contained in:
Eldinnie 2018-08-29 14:18:58 +02:00 committed by Jasmin Bom
parent 353410242f
commit 4689a80c2e
110 changed files with 4026 additions and 531 deletions

View file

@ -1,6 +1,49 @@
======= =======
Changes 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** **2018-05-02**
*Released 10.1.0* *Released 10.1.0*

View file

@ -95,7 +95,7 @@ make the development of bots easy and straightforward. These classes are contain
Telegram API support 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 Installing

View file

@ -0,0 +1,6 @@
telegram.Credentials
====================
.. autoclass:: telegram.Credentials
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.DataCredentials
========================
.. autoclass:: telegram.DataCredentials
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.EncryptedCredentials
=============================
.. autoclass:: telegram.EncryptedCredentials
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.EncryptedPassportElement
=================================
.. autoclass:: telegram.EncryptedPassportElement
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.FileCredentials
========================
.. autoclass:: telegram.FileCredentials
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.IdDocumentData
=======================
.. autoclass:: telegram.IdDocumentData
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.InputMediaAnimation
============================
.. autoclass:: telegram.InputMediaAnimation
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.InputMediaAudio
========================
.. autoclass:: telegram.InputMediaAudio
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.InputMediaDocument
===========================
.. autoclass:: telegram.InputMediaDocument
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PassportData
=====================
.. autoclass:: telegram.PassportData
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PassportElementError
=============================
.. autoclass:: telegram.PassportElementError
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PassportElementErrorDataField
======================================
.. autoclass:: telegram.PassportElementErrorDataField
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PassportElementErrorFile
=================================
.. autoclass:: telegram.PassportElementErrorFile
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PassportElementErrorFiles
==================================
.. autoclass:: telegram.PassportElementErrorFiles
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PassportElementErrorFrontSide
======================================
.. autoclass:: telegram.PassportElementErrorFrontSide
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PassportElementErrorReverseSide
========================================
.. autoclass:: telegram.PassportElementErrorReverseSide
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PassportFile
=====================
.. autoclass:: telegram.PassportFile
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PersonalDetails
========================
.. autoclass:: telegram.PersonalDetails
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.ResidentialAddress
===========================
.. autoclass:: telegram.ResidentialAddress
:members:
:show-inheritance:

View file

@ -5,6 +5,7 @@ telegram package
telegram.ext telegram.ext
telegram.utils telegram.utils
telegram.animation
telegram.audio telegram.audio
telegram.bot telegram.bot
telegram.callbackquery telegram.callbackquery
@ -22,6 +23,9 @@ telegram package
telegram.inlinekeyboardmarkup telegram.inlinekeyboardmarkup
telegram.inputfile telegram.inputfile
telegram.inputmedia telegram.inputmedia
telegram.inputmediaanimation
telegram.inputmediaaudio
telegram.inputmediadocument
telegram.inputmediaphoto telegram.inputmediaphoto
telegram.inputmediavideo telegram.inputmediavideo
telegram.keyboardbutton telegram.keyboardbutton
@ -106,10 +110,33 @@ Games
.. toctree:: .. toctree::
telegram.game telegram.game
telegram.animation
telegram.callbackgame telegram.callbackgame
telegram.gamehighscore 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 Module contents
--------------- ---------------

View file

@ -0,0 +1,6 @@
telegram.SecureData
===================
.. autoclass:: telegram.SecureData
:members:
:show-inheritance:

29
examples/passportbot.html Normal file
View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Telegram passport test!</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--- Needs file from https://github.com/TelegramMessenger/TGPassportJsSDK downloaded --->
<script src="telegram-passport.js"></script>
<script>
"use strict";
Telegram.Passport.createAuthButton('telegram_passport_auth', {
bot_id: BOT_ID, // YOUR BOT ID
scope: ['id_document', 'phone_number', 'email'], // WHAT DATA YOU WANT TO RECEIVE
public_key: '-----BEGIN PUBLIC KEY----- ...', // YOUR PUBLIC KEY
payload: 'thisisatest', // YOUR BOT WILL RECEIVE THIS DATA WITH THE REQUEST
callback_url: 'https://example.org' // TELEGRAM WILL SEND YOUR USER BACK TO THIS URL
});
</script>
</head>
<body>
<h1>Telegram passport test</h1>
<div id="telegram_passport_auth"></div>
</body>
</html>

99
examples/passportbot.py Normal file
View file

@ -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()

View file

@ -1,2 +1,3 @@
future>=0.16.0 future>=0.16.0
certifi certifi
cryptography

View file

@ -27,6 +27,7 @@ from .files.photosize import PhotoSize
from .files.audio import Audio from .files.audio import Audio
from .files.voice import Voice from .files.voice import Voice
from .files.document import Document from .files.document import Document
from .files.animation import Animation
from .files.sticker import Sticker, StickerSet, MaskPosition from .files.sticker import Sticker, StickerSet, MaskPosition
from .files.video import Video from .files.video import Video
from .files.contact import Contact from .files.contact import Contact
@ -45,13 +46,17 @@ from .files.inputfile import InputFile
from .files.file import File from .files.file import File
from .parsemode import ParseMode from .parsemode import ParseMode
from .messageentity import MessageEntity from .messageentity import MessageEntity
from .games.animation import Animation
from .games.game import Game from .games.game import Game
from .games.callbackgame import CallbackGame from .games.callbackgame import CallbackGame
from .payment.shippingaddress import ShippingAddress from .payment.shippingaddress import ShippingAddress
from .payment.orderinfo import OrderInfo from .payment.orderinfo import OrderInfo
from .payment.successfulpayment import SuccessfulPayment from .payment.successfulpayment import SuccessfulPayment
from .payment.invoice import Invoice 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 .message import Message
from .callbackquery import CallbackQuery from .callbackquery import CallbackQuery
from .choseninlineresult import ChosenInlineResult from .choseninlineresult import ChosenInlineResult
@ -91,14 +96,25 @@ from .payment.shippingquery import ShippingQuery
from .webhookinfo import WebhookInfo from .webhookinfo import WebhookInfo
from .games.gamehighscore import GameHighScore from .games.gamehighscore import GameHighScore
from .update import Update from .update import Update
from .files.inputmedia import (InputMedia, InputMediaVideo, InputMediaPhoto, InputMediaAnimation,
InputMediaAudio, InputMediaDocument)
from .bot import Bot from .bot import Bot
from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOOK_PORTS, from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOOK_PORTS,
MAX_FILESIZE_DOWNLOAD, MAX_FILESIZE_UPLOAD, MAX_FILESIZE_DOWNLOAD, MAX_FILESIZE_UPLOAD,
MAX_MESSAGES_PER_SECOND_PER_CHAT, MAX_MESSAGES_PER_SECOND, MAX_MESSAGES_PER_SECOND_PER_CHAT, MAX_MESSAGES_PER_SECOND,
MAX_MESSAGES_PER_MINUTE_PER_GROUP) MAX_MESSAGES_PER_MINUTE_PER_GROUP)
from .files.inputmedia import InputMedia from .passport.passportelementerrors import (PassportElementError,
from .files.inputmediavideo import InputMediaVideo PassportElementErrorDataField,
from .files.inputmediaphoto import InputMediaPhoto PassportElementErrorFile,
PassportElementErrorFiles,
PassportElementErrorFrontSide,
PassportElementErrorReverseSide,
PassportElementErrorSelfie)
from .passport.credentials import (Credentials,
DataCredentials,
SecureData,
FileCredentials,
TelegramDecryptionError)
from .version import __version__ # flake8: noqa from .version import __version__ # flake8: noqa
__author__ = 'devs@python-telegram-bot.org' __author__ = 'devs@python-telegram-bot.org'
@ -116,7 +132,8 @@ __all__ = [
'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo', 'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo',
'InlineQueryResultVoice', 'InlineQueryResultGame', 'InputContactMessageContent', 'InputFile', 'InlineQueryResultVoice', 'InlineQueryResultGame', 'InputContactMessageContent', 'InputFile',
'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent', 'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent',
'InputVenueMessageContent', 'KeyboardButton', 'Location', 'Message', 'MessageEntity', 'InputVenueMessageContent', 'KeyboardButton', 'Location', 'EncryptedCredentials',
'PassportFile', 'EncryptedPassportElement', 'PassportData', 'Message', 'MessageEntity',
'ParseMode', 'PhotoSize', 'ReplyKeyboardRemove', 'ReplyKeyboardMarkup', 'ReplyMarkup', 'ParseMode', 'PhotoSize', 'ReplyKeyboardRemove', 'ReplyKeyboardMarkup', 'ReplyMarkup',
'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue', 'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
'Video', 'Voice', 'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS', 'Video', 'Voice', 'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS',
@ -125,5 +142,11 @@ __all__ = [
'Game', 'GameHighScore', 'VideoNote', 'LabeledPrice', 'SuccessfulPayment', 'ShippingOption', 'Game', 'GameHighScore', 'VideoNote', 'LabeledPrice', 'SuccessfulPayment', 'ShippingOption',
'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery', 'ChatPhoto', 'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery', 'ChatPhoto',
'StickerSet', 'MaskPosition', 'CallbackGame', 'InputMedia', 'InputMediaPhoto', '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'
] ]

View file

@ -60,7 +60,12 @@ class TelegramObject(object):
data = dict() data = dict()
for key in iter(self.__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 continue
value = self.__dict__[key] value = self.__dict__[key]

View file

@ -21,17 +21,22 @@
"""This module contains an object that represents a Telegram Bot.""" """This module contains an object that represents a Telegram Bot."""
import functools import functools
import json try:
import ujson as json
except ImportError:
import json
import logging import logging
import warnings import warnings
from datetime import datetime from datetime import datetime
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from future.utils import string_types from future.utils import string_types
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File, from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet, ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet,
PhotoSize, Audio, Document, Sticker, Video, Voice, VideoNote, Location, PhotoSize, Audio, Document, Sticker, Video, Animation, Voice, VideoNote,
Venue, Contact) Location, Venue, Contact, InputFile)
from telegram.error import InvalidToken, TelegramError from telegram.error import InvalidToken, TelegramError
from telegram.utils.helpers import to_timestamp from telegram.utils.helpers import to_timestamp
from telegram.utils.request import Request from telegram.utils.request import Request
@ -101,10 +106,13 @@ class Bot(TelegramObject):
base_file_url (:obj:`str`, optional): Telegram Bot API file URL. base_file_url (:obj:`str`, optional): Telegram Bot API file URL.
request (:obj:`telegram.utils.request.Request`, optional): Pre initialized request (:obj:`telegram.utils.request.Request`, optional): Pre initialized
:obj:`telegram.utils.request.Request`. :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) self.token = self._validate_token(token)
if base_url is None: if base_url is None:
@ -119,6 +127,11 @@ class Bot(TelegramObject):
self._request = request or Request() self._request = request or Request()
self.logger = logging.getLogger(__name__) 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 @property
def request(self): def request(self):
return self._request return self._request
@ -381,6 +394,8 @@ class Bot(TelegramObject):
if isinstance(photo, PhotoSize): if isinstance(photo, PhotoSize):
photo = photo.file_id photo = photo.file_id
elif InputFile.is_file(photo):
photo = InputFile(photo)
data = {'chat_id': chat_id, 'photo': photo} data = {'chat_id': chat_id, 'photo': photo}
@ -405,6 +420,7 @@ class Bot(TelegramObject):
reply_markup=None, reply_markup=None,
timeout=20, timeout=20,
parse_mode=None, parse_mode=None,
thumb=None,
**kwargs): **kwargs):
""" """
Use this method to send audio files, if you want Telegram clients to display them in the 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 reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. 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). timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments. **kwargs (:obj:`dict`): Arbitrary keyword arguments.
@ -454,6 +474,8 @@ class Bot(TelegramObject):
if isinstance(audio, Audio): if isinstance(audio, Audio):
audio = audio.file_id audio = audio.file_id
elif InputFile.is_file(audio):
audio = InputFile(audio)
data = {'chat_id': chat_id, 'audio': audio} data = {'chat_id': chat_id, 'audio': audio}
@ -467,6 +489,10 @@ class Bot(TelegramObject):
data['caption'] = caption data['caption'] = caption
if parse_mode: if parse_mode:
data['parse_mode'] = 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 return url, data
@ -482,6 +508,7 @@ class Bot(TelegramObject):
reply_markup=None, reply_markup=None,
timeout=20, timeout=20,
parse_mode=None, parse_mode=None,
thumb=None,
**kwargs): **kwargs):
"""Use this method to send general files. """Use this method to send general files.
@ -511,6 +538,10 @@ class Bot(TelegramObject):
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. 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). timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments. **kwargs (:obj:`dict`): Arbitrary keyword arguments.
@ -525,15 +556,19 @@ class Bot(TelegramObject):
if isinstance(document, Document): if isinstance(document, Document):
document = document.file_id document = document.file_id
elif InputFile.is_file(document):
document = InputFile(document, filename=filename)
data = {'chat_id': chat_id, 'document': document} data = {'chat_id': chat_id, 'document': document}
if filename:
data['filename'] = filename
if caption: if caption:
data['caption'] = caption data['caption'] = caption
if parse_mode: if parse_mode:
data['parse_mode'] = 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 return url, data
@ -582,6 +617,8 @@ class Bot(TelegramObject):
if isinstance(sticker, Sticker): if isinstance(sticker, Sticker):
sticker = sticker.file_id sticker = sticker.file_id
elif InputFile.is_file(sticker):
sticker = InputFile(sticker)
data = {'chat_id': chat_id, 'sticker': sticker} data = {'chat_id': chat_id, 'sticker': sticker}
@ -602,6 +639,7 @@ class Bot(TelegramObject):
height=None, height=None,
parse_mode=None, parse_mode=None,
supports_streaming=None, supports_streaming=None,
thumb=None,
**kwargs): **kwargs):
""" """
Use this method to send video files, Telegram clients support mp4 videos 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 reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user. 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). timeout (:obj:`int` | :obj:`float`, optional): Send file timeout (default: 20 seconds).
**kwargs (:obj:`dict`): Arbitrary keyword arguments. **kwargs (:obj:`dict`): Arbitrary keyword arguments.
@ -650,6 +692,8 @@ class Bot(TelegramObject):
if isinstance(video, Video): if isinstance(video, Video):
video = video.file_id video = video.file_id
elif InputFile.is_file(video):
video = InputFile(video)
data = {'chat_id': chat_id, 'video': video} data = {'chat_id': chat_id, 'video': video}
@ -665,6 +709,162 @@ class Bot(TelegramObject):
data['width'] = width data['width'] = width
if height: if height:
data['height'] = 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 return url, data
@ -724,6 +924,8 @@ class Bot(TelegramObject):
if isinstance(voice, Voice): if isinstance(voice, Voice):
voice = voice.file_id voice = voice.file_id
elif InputFile.is_file(voice):
voice = InputFile(voice)
data = {'chat_id': chat_id, 'voice': voice} data = {'chat_id': chat_id, 'voice': voice}
@ -736,65 +938,6 @@ class Bot(TelegramObject):
return url, data 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 @log
def send_media_group(self, def send_media_group(self,
chat_id, chat_id,
@ -823,12 +966,9 @@ class Bot(TelegramObject):
Raises: Raises:
:class:`telegram.TelegramError` :class:`telegram.TelegramError`
""" """
# TODO: Make InputMediaPhoto, InputMediaVideo and send_media_group work with new files
url = '{0}/sendMediaGroup'.format(self.base_url) url = '{0}/sendMediaGroup'.format(self.base_url)
media = [med.to_dict() for med in media]
data = {'chat_id': chat_id, 'media': media} data = {'chat_id': chat_id, 'media': media}
if reply_to_message_id: if reply_to_message_id:
@ -1025,12 +1165,14 @@ class Bot(TelegramObject):
reply_markup=None, reply_markup=None,
timeout=None, timeout=None,
venue=None, venue=None,
foursquare_type=None,
**kwargs): **kwargs):
"""Use this method to send information about a venue. """Use this method to send information about a venue.
Note: Note:
you can either supply :obj:`venue`, or :obj:`latitude`, :obj:`longitude`, 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: Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username 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. title (:obj:`str`, optional): Name of the venue.
address (:obj:`str`, optional): Address of the venue. address (:obj:`str`, optional): Address of the venue.
foursquare_id (:obj:`str`, optional): Foursquare identifier 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. venue (:class:`telegram.Venue`, optional): The venue to send.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound. receive a notification with no sound.
@ -1072,6 +1217,7 @@ class Bot(TelegramObject):
address = venue.address address = venue.address
title = venue.title title = venue.title
foursquare_id = venue.foursquare_id foursquare_id = venue.foursquare_id
foursquare_type = venue.foursquare_type
data = { data = {
'chat_id': chat_id, 'chat_id': chat_id,
@ -1083,6 +1229,8 @@ class Bot(TelegramObject):
if foursquare_id: if foursquare_id:
data['foursquare_id'] = foursquare_id data['foursquare_id'] = foursquare_id
if foursquare_type:
data['foursquare_type'] = foursquare_type
return url, data return url, data
@ -1098,12 +1246,13 @@ class Bot(TelegramObject):
reply_markup=None, reply_markup=None,
timeout=None, timeout=None,
contact=None, contact=None,
vcard=None,
**kwargs): **kwargs):
"""Use this method to send phone contacts. """Use this method to send phone contacts.
Note: Note:
You can either supply :obj:`contact` or :obj:`phone_number` and :obj:`first_name` 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: Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username 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. phone_number (:obj:`str`, optional): Contact's phone number.
first_name (:obj:`str`, optional): Contact's first name. first_name (:obj:`str`, optional): Contact's first name.
last_name (:obj:`str`, optional): Contact's last 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. contact (:class:`telegram.Contact`, optional): The contact to send.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound. receive a notification with no sound.
@ -1141,11 +1292,14 @@ class Bot(TelegramObject):
phone_number = contact.phone_number phone_number = contact.phone_number
first_name = contact.first_name first_name = contact.first_name
last_name = contact.last_name last_name = contact.last_name
vcard = contact.vcard
data = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name} data = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name}
if last_name: if last_name:
data['last_name'] = last_name data['last_name'] = last_name
if vcard:
data['vcard'] = vcard
return url, data return url, data
@ -1648,6 +1802,59 @@ class Bot(TelegramObject):
return url, data 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 @log
@message @message
def edit_message_reply_markup(self, def edit_message_reply_markup(self,
@ -1851,6 +2058,8 @@ class Bot(TelegramObject):
if url is not None: if url is not None:
data['url'] = url data['url'] = url
if certificate: if certificate:
if InputFile.is_file(certificate):
certificate = InputFile(certificate)
data['certificate'] = certificate data['certificate'] = certificate
if max_connections is not None: if max_connections is not None:
data['max_connections'] = max_connections data['max_connections'] = max_connections
@ -2658,6 +2867,9 @@ class Bot(TelegramObject):
""" """
url = '{0}/setChatPhoto'.format(self.base_url) url = '{0}/setChatPhoto'.format(self.base_url)
if InputFile.is_file(photo):
photo = InputFile(photo)
data = {'chat_id': chat_id, 'photo': photo} data = {'chat_id': chat_id, 'photo': photo}
data.update(kwargs) data.update(kwargs)
@ -2892,6 +3104,9 @@ class Bot(TelegramObject):
""" """
url = '{0}/uploadStickerFile'.format(self.base_url) 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 = {'user_id': user_id, 'png_sticker': png_sticker}
data.update(kwargs) data.update(kwargs)
@ -2943,6 +3158,9 @@ class Bot(TelegramObject):
""" """
url = '{0}/createNewStickerSet'.format(self.base_url) 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, data = {'user_id': user_id, 'name': name, 'title': title, 'png_sticker': png_sticker,
'emojis': emojis} 'emojis': emojis}
@ -2991,6 +3209,9 @@ class Bot(TelegramObject):
""" """
url = '{0}/addStickerToSet'.format(self.base_url) 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} data = {'user_id': user_id, 'name': name, 'png_sticker': png_sticker, 'emojis': emojis}
if mask_position is not None: if mask_position is not None:
@ -3056,6 +3277,44 @@ class Bot(TelegramObject):
return result 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): def to_dict(self):
data = {'id': self.id, 'username': self.username, 'first_name': self.username} data = {'id': self.id, 'username': self.username, 'first_name': self.username}
@ -3087,6 +3346,8 @@ class Bot(TelegramObject):
"""Alias for :attr:`send_sticker`""" """Alias for :attr:`send_sticker`"""
sendVideo = send_video sendVideo = send_video
"""Alias for :attr:`send_video`""" """Alias for :attr:`send_video`"""
sendAnimation = send_animation
"""Alias for :attr:`send_animation`"""
sendVoice = send_voice sendVoice = send_voice
"""Alias for :attr:`send_voice`""" """Alias for :attr:`send_voice`"""
sendVideoNote = send_video_note sendVideoNote = send_video_note
@ -3123,6 +3384,8 @@ class Bot(TelegramObject):
"""Alias for :attr:`edit_message_text`""" """Alias for :attr:`edit_message_text`"""
editMessageCaption = edit_message_caption editMessageCaption = edit_message_caption
"""Alias for :attr:`edit_message_caption`""" """Alias for :attr:`edit_message_caption`"""
editMessageMedia = edit_message_media
"""Alias for :attr:`edit_message_media`"""
editMessageReplyMarkup = edit_message_reply_markup editMessageReplyMarkup = edit_message_reply_markup
"""Alias for :attr:`edit_message_reply_markup`""" """Alias for :attr:`edit_message_reply_markup`"""
getUpdates = get_updates getUpdates = get_updates
@ -3187,3 +3450,5 @@ class Bot(TelegramObject):
"""Alias for :attr:`set_sticker_position_in_set`""" """Alias for :attr:`set_sticker_position_in_set`"""
deleteStickerFromSet = delete_sticker_from_set deleteStickerFromSet = delete_sticker_from_set
"""Alias for :attr:`delete_sticker_from_set`""" """Alias for :attr:`delete_sticker_from_set`"""
setPassportDataErrors = set_passport_data_errors
"""Alias for :attr:`set_passport_data_errors`"""

View file

@ -273,6 +273,19 @@ class Chat(TelegramObject):
""" """
return self.bot.send_document(self.id, *args, **kwargs) 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): def send_sticker(self, *args, **kwargs):
"""Shortcut for:: """Shortcut for::

View file

@ -50,7 +50,7 @@ class CallbackQueryHandler(Handler):
Note: Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you :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 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``. or in the same chat, it will be the same ``dict``.

View file

@ -39,7 +39,7 @@ class ChosenInlineResultHandler(Handler):
Note: Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you :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 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``. or in the same chat, it will be the same ``dict``.

View file

@ -52,7 +52,7 @@ class CommandHandler(Handler):
Note: Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you :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 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``. or in the same chat, it will be the same ``dict``.

View file

@ -143,7 +143,7 @@ class Dispatcher(object):
""" """
if cls.__singleton is not None: if cls.__singleton is not None:
return cls.__singleton() return cls.__singleton() # pylint: disable=not-callable
else: else:
raise RuntimeError('{} not initialized or multiple instances exist'.format( raise RuntimeError('{} not initialized or multiple instances exist'.format(
cls.__name__)) cls.__name__))
@ -385,4 +385,4 @@ class Dispatcher(object):
else: else:
self.logger.exception( self.logger.exception(
'No error handlers are registered, logging exception...', exc_info=error) 'No error handlers are registered, logging exception.', exc_info=error)

View file

@ -302,6 +302,15 @@ class Filters(object):
document = _Document() document = _Document()
""":obj:`Filter`: Messages that contain :class:`telegram.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): class _Photo(BaseFilter):
name = 'Filters.photo' name = 'Filters.photo'
@ -677,6 +686,15 @@ class Filters(object):
successful_payment = _SuccessfulPayment() successful_payment = _SuccessfulPayment()
""":obj:`Filter`: Messages that confirm a :class:`telegram.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): class language(BaseFilter):
"""Filters messages to only allow those which are from users with a certain language code. """Filters messages to only allow those which are from users with a certain language code.

View file

@ -35,7 +35,7 @@ class Handler(object):
Note: Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you :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 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``. or in the same chat, it will be the same ``dict``.

View file

@ -50,7 +50,7 @@ class InlineQueryHandler(Handler):
Note: Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you :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 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``. or in the same chat, it will be the same ``dict``.

View file

@ -50,7 +50,7 @@ class MessageHandler(Handler):
Note: Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you :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 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``. or in the same chat, it will be the same ``dict``.

View file

@ -38,7 +38,7 @@ class PreCheckoutQueryHandler(Handler):
Note: Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you :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 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``. or in the same chat, it will be the same ``dict``.

View file

@ -53,7 +53,7 @@ class RegexHandler(Handler):
Note: Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you :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 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``. or in the same chat, it will be the same ``dict``.

View file

@ -38,7 +38,7 @@ class ShippingQueryHandler(Handler):
Note: Note:
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you :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 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``. or in the same chat, it will be the same ``dict``.

View file

@ -66,6 +66,8 @@ class Updater(object):
bot (:class:`telegram.Bot`, optional): A pre-initialized bot instance. If a pre-initialized 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` bot is used, it is the user's responsibility to create it using a `Request`
instance with a large enough connection pool. 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 user_sig_handler (:obj:`function`, optional): Takes ``signum, frame`` as positional
arguments. This will be called when a signal is received, defaults are (SIGINT, arguments. This will be called when a signal is received, defaults are (SIGINT,
SIGTERM, SIGABRT) setable with :attr:`idle`. SIGTERM, SIGABRT) setable with :attr:`idle`.
@ -89,6 +91,8 @@ class Updater(object):
base_url=None, base_url=None,
workers=4, workers=4,
bot=None, bot=None,
private_key=None,
private_key_password=None,
user_sig_handler=None, user_sig_handler=None,
request_kwargs=None): request_kwargs=None):
@ -96,6 +100,8 @@ class Updater(object):
raise ValueError('`token` or `bot` must be passed') raise ValueError('`token` or `bot` must be passed')
if (token is not None) and (bot is not None): if (token is not None) and (bot is not None):
raise ValueError('`token` and `bot` are mutually exclusive') 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__) self.logger = logging.getLogger(__name__)
@ -119,7 +125,8 @@ class Updater(object):
if 'con_pool_size' not in request_kwargs: if 'con_pool_size' not in request_kwargs:
request_kwargs['con_pool_size'] = con_pool_size request_kwargs['con_pool_size'] = con_pool_size
self._request = Request(**request_kwargs) 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.user_sig_handler = user_sig_handler
self.update_queue = Queue() self.update_queue = Queue()
self.job_queue = JobQueue(self.bot) self.job_queue = JobQueue(self.bot)

View file

@ -26,6 +26,9 @@ class Animation(TelegramObject):
Attributes: Attributes:
file_id (:obj:`str`): Unique file identifier. 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 thumb (:class:`telegram.PhotoSize`): Optional. Animation thumbnail as defined
by sender. by sender.
file_name (:obj:`str`): Optional. Original animation filename as defined by sender. file_name (:obj:`str`): Optional. Original animation filename as defined by sender.
@ -34,6 +37,9 @@ class Animation(TelegramObject):
Args: Args:
file_id (:obj:`str`): Unique file identifier. 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. thumb (:class:`telegram.PhotoSize`, optional): Animation thumbnail as defined by sender.
file_name (:obj:`str`, optional): Original animation filename 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. mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
@ -43,12 +49,19 @@ class Animation(TelegramObject):
def __init__(self, def __init__(self,
file_id, file_id,
width,
height,
duration,
thumb=None, thumb=None,
file_name=None, file_name=None,
mime_type=None, mime_type=None,
file_size=None, file_size=None,
**kwargs): **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.thumb = thumb
self.file_name = file_name self.file_name = file_name
self.mime_type = mime_type self.mime_type = mime_type

View file

@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Audio.""" """This module contains an object that represents a Telegram Audio."""
from telegram import TelegramObject from telegram import TelegramObject, PhotoSize
class Audio(TelegramObject): 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. 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. mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
file_size (:obj:`int`): Optional. File size. 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. bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
Args: Args:
@ -42,6 +44,8 @@ class Audio(TelegramObject):
title (:obj:`str`, optional): Title 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.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender. mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
file_size (:obj:`int`, optional): File size. 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. bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
**kwargs (:obj:`dict`): Arbitrary keyword arguments. **kwargs (:obj:`dict`): Arbitrary keyword arguments.
@ -54,6 +58,7 @@ class Audio(TelegramObject):
title=None, title=None,
mime_type=None, mime_type=None,
file_size=None, file_size=None,
thumb=None,
bot=None, bot=None,
**kwargs): **kwargs):
# Required # Required
@ -64,6 +69,7 @@ class Audio(TelegramObject):
self.title = title self.title = title
self.mime_type = mime_type self.mime_type = mime_type
self.file_size = file_size self.file_size = file_size
self.thumb = thumb
self.bot = bot self.bot = bot
self._id_attrs = (self.file_id,) self._id_attrs = (self.file_id,)
@ -73,6 +79,8 @@ class Audio(TelegramObject):
if not data: if not data:
return None return None
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
return cls(bot=bot, **data) return cls(bot=bot, **data)
def get_file(self, timeout=None, **kwargs): def get_file(self, timeout=None, **kwargs):

View file

@ -29,23 +29,27 @@ class Contact(TelegramObject):
first_name (:obj:`str`): Contact's first name. first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`): Optional. Contact's last name. last_name (:obj:`str`): Optional. Contact's last name.
user_id (:obj:`int`): Optional. Contact's user identifier in Telegram. 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: Args:
phone_number (:obj:`str`): Contact's phone number. phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name. first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`, optional): Contact's last name. last_name (:obj:`str`, optional): Contact's last name.
user_id (:obj:`int`, optional): Contact's user identifier in Telegram. 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. **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 # Required
self.phone_number = str(phone_number) self.phone_number = str(phone_number)
self.first_name = first_name self.first_name = first_name
# Optionals # Optionals
self.last_name = last_name self.last_name = last_name
self.user_id = user_id self.user_id = user_id
self.vcard = vcard
self._id_attrs = (self.phone_number,) self._id_attrs = (self.phone_number,)

View file

@ -22,6 +22,7 @@ from os.path import basename
from future.backports.urllib import parse as urllib_parse from future.backports.urllib import parse as urllib_parse
from telegram import TelegramObject from telegram import TelegramObject
from telegram.passport.credentials import decrypt
class File(TelegramObject): class File(TelegramObject):
@ -45,6 +46,10 @@ class File(TelegramObject):
bot (:obj:`telegram.Bot`, optional): Bot to use with shortcut method. bot (:obj:`telegram.Bot`, optional): Bot to use with shortcut method.
**kwargs (:obj:`dict`): Arbitrary keyword arguments. **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): 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.file_path = file_path
self.bot = bot self.bot = bot
self._credentials = None
self._id_attrs = (self.file_id,) self._id_attrs = (self.file_id,)
@ -100,6 +106,8 @@ class File(TelegramObject):
if out: if out:
buf = self.bot.request.retrieve(url) buf = self.bot.request.retrieve(url)
if self._credentials:
buf = decrypt(self._credentials.secret, self._credentials.hash, buf, file=True)
out.write(buf) out.write(buf)
return out return out
else: else:
@ -108,7 +116,11 @@ class File(TelegramObject):
else: else:
filename = basename(self.file_path) 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 return filename
def _get_encoded_url(self): def _get_encoded_url(self):
@ -133,3 +145,6 @@ class File(TelegramObject):
buf.extend(self.bot.request.retrieve(self._get_encoded_url())) buf.extend(self.bot.request.retrieve(self._get_encoded_url()))
return buf return buf
def set_credentials(self, credentials):
self._credentials = credentials

View file

@ -19,132 +19,68 @@
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram InputFile.""" """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 imghdr
import mimetypes import mimetypes
import os import os
import sys import sys
from uuid import uuid4
from telegram import TelegramError from telegram import TelegramError
DEFAULT_MIME_TYPE = 'application/octet-stream' DEFAULT_MIME_TYPE = 'application/octet-stream'
USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)'
FILE_TYPES = ('audio', 'document', 'photo', 'sticker', 'video', 'voice', 'certificate',
'video_note', 'png_sticker')
class InputFile(object): class InputFile(object):
"""This object represents a Telegram InputFile. """This object represents a Telegram InputFile.
Attributes: 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: 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: Raises:
TelegramError TelegramError
""" """
def __init__(self, data): def __init__(self, obj, filename=None, attach=None):
self.data = data
self.boundary = choose_boundary()
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 hasattr(self.input_file, 'read'):
self.filename = None self.filename = None
self.input_file_content = self.input_file.read() self.input_file_content = obj.read()
if 'filename' in data: self.attach = 'attached' + uuid4().hex if attach else None
self.filename = self.data.pop('filename')
elif (hasattr(self.input_file, 'name') and if filename:
not isinstance(self.input_file.name, int) and # py3 self.filename = filename
self.input_file.name != '<fdopen>'): # py2 elif (hasattr(obj, 'name') and
not isinstance(obj.name, int) and # py3
obj.name != '<fdopen>'): # py2
# on py2.7, pylint fails to understand this properly # on py2.7, pylint fails to understand this properly
# pylint: disable=E1101 # pylint: disable=E1101
self.filename = os.path.basename(self.input_file.name) self.filename = os.path.basename(obj.name)
try: try:
self.mimetype = self.is_image(self.input_file_content) 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: except TelegramError:
if self.filename: if self.filename:
self.mimetype = mimetypes.guess_type( self.mimetype = mimetypes.guess_type(
self.filename)[0] or DEFAULT_MIME_TYPE self.filename)[0] or DEFAULT_MIME_TYPE
else: else:
self.mimetype = DEFAULT_MIME_TYPE 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 sys.version_info < (3,):
if isinstance(self.filename, unicode): # flake8: noqa pylint: disable=E0602 if isinstance(self.filename, unicode): # flake8: noqa pylint: disable=E0602
self.filename = self.filename.encode('utf-8', 'replace') self.filename = self.filename.encode('utf-8', 'replace')
@property @property
def headers(self): def field_tuple(self):
""":obj:`dict`: Headers.""" return self.filename, self.input_file_content, self.mimetype
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)
@staticmethod @staticmethod
def is_image(stream): def is_image(stream):
@ -164,22 +100,9 @@ class InputFile(object):
raise TelegramError('Could not parse file content') raise TelegramError('Could not parse file content')
@staticmethod @staticmethod
def is_inputfile(data): def is_file(obj):
"""Check if the request is a file request. return hasattr(obj, 'read')
Args: def to_dict(self):
data (Dict[:obj:`str`, :obj:`str`]): A dict of (str, str) key/value pairs. if self.attach:
return 'attach://' + self.attach
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

View file

@ -18,14 +18,334 @@
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""Base class for Telegram InputMedia Objects.""" """Base class for Telegram InputMedia Objects."""
from telegram import TelegramObject from telegram import TelegramObject, InputFile, PhotoSize, Animation, Video, Audio, Document
class InputMedia(TelegramObject): class InputMedia(TelegramObject):
"""Base class for Telegram InputMedia Objects. """Base class for Telegram InputMedia Objects.
See :class:`telegram.InputMediaPhoto` and :class:`telegram.InputMediaVideo` for See :class:`telegram.InputMediaAnimation`, :class:`telegram.InputMediaAudio`,
detailed use. :class:`telegram.InputMediaDocument`, :class:`telegram.InputMediaPhoto` and
:class:`telegram.InputMediaVideo` for detailed use.
""" """
pass 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

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram 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

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram 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

View file

@ -29,23 +29,29 @@ class Venue(TelegramObject):
title (:obj:`str`): Name of the venue. title (:obj:`str`): Name of the venue.
address (:obj:`str`): Address of the venue. address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`): Optional. Foursquare identifier 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: Args:
location (:class:`telegram.Location`): Venue location. location (:class:`telegram.Location`): Venue location.
title (:obj:`str`): Name of the venue. title (:obj:`str`): Name of the venue.
address (:obj:`str`): Address of the venue. address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`, optional): Foursquare identifier 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. **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 # Required
self.location = location self.location = location
self.title = title self.title = title
self.address = address self.address = address
# Optionals # Optionals
self.foursquare_id = foursquare_id self.foursquare_id = foursquare_id
self.foursquare_type = foursquare_type
self._id_attrs = (self.location, self.title) self._id_attrs = (self.location, self.title)

View file

@ -36,7 +36,7 @@ class InlineQueryResultAudio(InlineQueryResult):
audio_duration (:obj:`str`): Optional. Performer. audio_duration (:obj:`str`): Optional. Performer.
caption (:obj:`str`): Optional. Audio duration in seconds. caption (:obj:`str`): Optional. Audio duration in seconds.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -51,7 +51,7 @@ class InlineQueryResultAudio(InlineQueryResult):
audio_duration (:obj:`str`, optional): Performer. audio_duration (:obj:`str`, optional): Performer.
caption (:obj:`str`, optional): Audio duration in seconds. caption (:obj:`str`, optional): Audio duration in seconds.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -33,7 +33,7 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
audio_file_id (:obj:`str`): A valid file identifier for the audio file. audio_file_id (:obj:`str`): A valid file identifier for the audio file.
caption (:obj:`str`): Optional. Caption, 0-200 characters caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -45,7 +45,7 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
audio_file_id (:obj:`str`): A valid file identifier for the audio file. audio_file_id (:obj:`str`): A valid file identifier for the audio file.
caption (:obj:`str`, optional): Caption, 0-200 characters caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -35,7 +35,7 @@ class InlineQueryResultCachedGif(InlineQueryResult):
title (:obj:`str`): Optional. Title for the result. title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -48,7 +48,7 @@ class InlineQueryResultCachedGif(InlineQueryResult):
title (:obj:`str`, optional): Title for the result.caption (:obj:`str`, optional): title (:obj:`str`, optional): Title for the result.caption (:obj:`str`, optional):
caption (:obj:`str`, optional): Caption, 0-200 characters caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -35,7 +35,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
title (:obj:`str`): Optional. Title for the result. title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -48,7 +48,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
title (:obj:`str`, optional): Title for the result. title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption, 0-200 characters caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -36,7 +36,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
description (:obj:`str`): Optional. Short description of the result. description (:obj:`str`): Optional. Short description of the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -50,7 +50,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
description (:obj:`str`, optional): Short description of the result. description (:obj:`str`, optional): Short description of the result.
caption (:obj:`str`, optional): Caption, 0-200 characters caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -36,7 +36,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
description (:obj:`str`): Optional. Short description of the result. description (:obj:`str`): Optional. Short description of the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters. caption (:obj:`str`): Optional. Caption, 0-200 characters.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -50,7 +50,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
description (:obj:`str`, optional): Short description of the result. description (:obj:`str`, optional): Short description of the result.
caption (:obj:`str`, optional): Caption, 0-200 characters. caption (:obj:`str`, optional): Caption, 0-200 characters.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -34,7 +34,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
title (:obj:`str`): Voice message title. title (:obj:`str`): Voice message title.
caption (:obj:`str`): Optional. Caption, 0-200 characters. caption (:obj:`str`): Optional. Caption, 0-200 characters.
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -47,7 +47,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
title (:obj:`str`): Voice message title. title (:obj:`str`): Voice message title.
caption (:obj:`str`, optional): Caption, 0-200 characters. caption (:obj:`str`, optional): Caption, 0-200 characters.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -33,6 +33,8 @@ class InlineQueryResultContact(InlineQueryResult):
phone_number (:obj:`str`): Contact's phone number. phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name. first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`): Optional. Contact's last 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 reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the 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. phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name. first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`, optional): Contact's last 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 reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
@ -67,6 +71,7 @@ class InlineQueryResultContact(InlineQueryResult):
thumb_url=None, thumb_url=None,
thumb_width=None, thumb_width=None,
thumb_height=None, thumb_height=None,
vcard=None,
**kwargs): **kwargs):
# Required # Required
super(InlineQueryResultContact, self).__init__('contact', id) super(InlineQueryResultContact, self).__init__('contact', id)
@ -76,6 +81,8 @@ class InlineQueryResultContact(InlineQueryResult):
# Optionals # Optionals
if last_name: if last_name:
self.last_name = last_name self.last_name = last_name
if vcard:
self.vcard = vcard
if reply_markup: if reply_markup:
self.reply_markup = reply_markup self.reply_markup = reply_markup
if input_message_content: if input_message_content:

View file

@ -34,7 +34,7 @@ class InlineQueryResultDocument(InlineQueryResult):
title (:obj:`str`): Title for the result. title (:obj:`str`): Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
document_url (:obj:`str`): A valid URL for the file. 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" 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. title (:obj:`str`): Title for the result.
caption (:obj:`str`, optional): Caption, 0-200 characters caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
document_url (:obj:`str`): A valid URL for the file. 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" mime_type (:obj:`str`): Mime type of the content of the file, either "application/pdf"

View file

@ -38,7 +38,7 @@ class InlineQueryResultGif(InlineQueryResult):
title (:obj:`str`): Optional. Title for the result. title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -55,7 +55,7 @@ class InlineQueryResultGif(InlineQueryResult):
title (:obj:`str`, optional): Title for the result.caption (:obj:`str`, optional): title (:obj:`str`, optional): Title for the result.caption (:obj:`str`, optional):
caption (:obj:`str`, optional): Caption, 0-200 characters caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -39,7 +39,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
title (:obj:`str`): Optional. Title for the result. title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -56,7 +56,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
title (:obj:`str`, optional): Title for the result. title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption, 0-200 characters caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -39,7 +39,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
description (:obj:`str`): Optional. Short description of the result. description (:obj:`str`): Optional. Short description of the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
@ -57,7 +57,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
description (:obj:`str`, optional): Short description of the result. description (:obj:`str`, optional): Short description of the result.
caption (:obj:`str`, optional): Caption, 0-200 characters caption (:obj:`str`, optional): Caption, 0-200 characters
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.

View file

@ -35,6 +35,9 @@ class InlineQueryResultVenue(InlineQueryResult):
title (:obj:`str`): Title of the venue. title (:obj:`str`): Title of the venue.
address (:obj:`str`): Address of the venue. address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue if known. 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 reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message. to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
@ -50,6 +53,9 @@ class InlineQueryResultVenue(InlineQueryResult):
title (:obj:`str`): Title of the venue. title (:obj:`str`): Title of the venue.
address (:obj:`str`): Address of the venue. address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue if known. 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 reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
@ -68,6 +74,7 @@ class InlineQueryResultVenue(InlineQueryResult):
title, title,
address, address,
foursquare_id=None, foursquare_id=None,
foursquare_type=None,
reply_markup=None, reply_markup=None,
input_message_content=None, input_message_content=None,
thumb_url=None, thumb_url=None,
@ -85,6 +92,8 @@ class InlineQueryResultVenue(InlineQueryResult):
# Optional # Optional
if foursquare_id: if foursquare_id:
self.foursquare_id = foursquare_id self.foursquare_id = foursquare_id
if foursquare_type:
self.foursquare_type = foursquare_type
if reply_markup: if reply_markup:
self.reply_markup = reply_markup self.reply_markup = reply_markup
if input_message_content: if input_message_content:

View file

@ -37,7 +37,7 @@ class InlineQueryResultVideo(InlineQueryResult):
title (:obj:`str`): Title for the result. title (:obj:`str`): Title for the result.
caption (:obj:`str`): Optional. Caption, 0-200 characters caption (:obj:`str`): Optional. Caption, 0-200 characters
parse_mode (:obj:`str`): Optional. Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
video_width (:obj:`int`): Optional. Video width. video_width (:obj:`int`): Optional. Video width.
video_height (:obj:`int`): Optional. Video height. video_height (:obj:`int`): Optional. Video height.
@ -56,7 +56,7 @@ class InlineQueryResultVideo(InlineQueryResult):
title (:obj:`str`): Title for the result. title (:obj:`str`): Title for the result.
caption (:obj:`str`, optional): Caption, 0-200 characters. caption (:obj:`str`, optional): Caption, 0-200 characters.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show 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. in :class:`telegram.ParseMode` for the available modes.
video_width (:obj:`int`, optional): Video width. video_width (:obj:`int`, optional): Video width.
video_height (:obj:`int`, optional): Video height. video_height (:obj:`int`, optional): Video height.

View file

@ -28,18 +28,23 @@ class InputContactMessageContent(InputMessageContent):
phone_number (:obj:`str`): Contact's phone number. phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name. first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`): Optional. Contact's last 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: Args:
phone_number (:obj:`str`): Contact's phone number. phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name. first_name (:obj:`str`): Contact's first name.
last_name (:obj:`str`, optional): Contact's last 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. **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 # Required
self.phone_number = phone_number self.phone_number = phone_number
self.first_name = first_name self.first_name = first_name
# Optionals # Optionals
self.last_name = last_name self.last_name = last_name
self.vcard = vcard

View file

@ -30,6 +30,9 @@ class InputVenueMessageContent(InputMessageContent):
title (:obj:`str`): Name of the venue. title (:obj:`str`): Name of the venue.
address (:obj:`str`): Address of the venue. address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue, if known. 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: Args:
latitude (:obj:`float`): Latitude of the location in degrees. latitude (:obj:`float`): Latitude of the location in degrees.
@ -37,11 +40,15 @@ class InputVenueMessageContent(InputMessageContent):
title (:obj:`str`): Name of the venue. title (:obj:`str`): Name of the venue.
address (:obj:`str`): Address of the venue. address (:obj:`str`): Address of the venue.
foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue, if known. 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. **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 # Required
self.latitude = latitude self.latitude = latitude
self.longitude = longitude self.longitude = longitude
@ -49,3 +56,4 @@ class InputVenueMessageContent(InputMessageContent):
self.address = address self.address = address
# Optionals # Optionals
self.foursquare_id = foursquare_id self.foursquare_id = foursquare_id
self.foursquare_type = foursquare_type

View file

@ -21,9 +21,9 @@
import sys import sys
from html import escape from html import escape
from telegram import (Audio, Contact, Document, Chat, Location, PhotoSize, Sticker, TelegramObject, from telegram import (Animation, Audio, Contact, Document, Chat, Location, PhotoSize, Sticker,
User, Video, Voice, Venue, MessageEntity, Game, Invoice, SuccessfulPayment, TelegramObject, User, Video, Voice, Venue, MessageEntity, Game, Invoice,
VideoNote) SuccessfulPayment, VideoNote, PassportData)
from telegram import ParseMode from telegram import ParseMode
from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp
@ -62,6 +62,9 @@ class Message(TelegramObject):
to use properly. to use properly.
audio (:class:`telegram.Audio`): Optional. Information about the file. audio (:class:`telegram.Audio`): Optional. Information about the file.
document (:class:`telegram.Document`): 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. game (:class:`telegram.Game`): Optional. Information about the game.
photo (List[:class:`telegram.PhotoSize`]): Optional. Available sizes of the photo. photo (List[:class:`telegram.PhotoSize`]): Optional. Available sizes of the photo.
sticker (:class:`telegram.Sticker`): Optional. Information about the sticker. sticker (:class:`telegram.Sticker`): Optional. Information about the sticker.
@ -97,6 +100,7 @@ class Message(TelegramObject):
forwarded from channels. forwarded from channels.
author_signature (:obj:`str`): Optional. Signature of the post author for messages author_signature (:obj:`str`): Optional. Signature of the post author for messages
in channels. in channels.
passport_data (:class:`telegram.PassportData`): Optional. Telegram Passport data
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
Args: Args:
@ -134,6 +138,9 @@ class Message(TelegramObject):
about the file. about the file.
document (:class:`telegram.Document`, optional): Message is a general file, information document (:class:`telegram.Document`, optional): Message is a general file, information
about the file. 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. game (:class:`telegram.Game`, optional): Message is a game, information about the game.
photo (List[:class:`telegram.PhotoSize`], optional): Message is a photo, available photo (List[:class:`telegram.PhotoSize`], optional): Message is a photo, available
sizes of the photo. sizes of the photo.
@ -192,17 +199,18 @@ class Message(TelegramObject):
forwarded from channels. forwarded from channels.
author_signature (:obj:`str`, optional): Signature of the post author for messages author_signature (:obj:`str`, optional): Signature of the post author for messages
in channels. in channels.
passport_data (:class:`telegram.PassportData`, optional): Telegram Passport data
""" """
_effective_attachment = _UNDEFINED _effective_attachment = _UNDEFINED
ATTACHMENT_TYPES = ['audio', 'game', 'document', 'photo', 'sticker', 'video', 'voice', ATTACHMENT_TYPES = ['audio', 'game', 'animation', 'document', 'photo', 'sticker', 'video',
'video_note', 'contact', 'location', 'venue', 'invoice', 'voice', 'video_note', 'contact', 'location', 'venue', 'invoice',
'successful_payment'] 'successful_payment']
MESSAGE_TYPES = ['text', 'new_chat_members', 'new_chat_title', 'new_chat_photo', MESSAGE_TYPES = ['text', 'new_chat_members', 'new_chat_title', 'new_chat_photo',
'delete_chat_photo', 'group_chat_created', 'supergroup_chat_created', 'delete_chat_photo', 'group_chat_created', 'supergroup_chat_created',
'channel_chat_created', 'migrate_to_chat_id', 'migrate_from_chat_id', 'channel_chat_created', 'migrate_to_chat_id', 'migrate_from_chat_id',
'pinned_message'] + ATTACHMENT_TYPES 'pinned_message', 'passport_data'] + ATTACHMENT_TYPES
def __init__(self, def __init__(self,
message_id, message_id,
@ -247,6 +255,8 @@ class Message(TelegramObject):
author_signature=None, author_signature=None,
media_group_id=None, media_group_id=None,
connected_website=None, connected_website=None,
animation=None,
passport_data=None,
bot=None, bot=None,
**kwargs): **kwargs):
# Required # Required
@ -293,6 +303,8 @@ class Message(TelegramObject):
self.forward_signature = forward_signature self.forward_signature = forward_signature
self.author_signature = author_signature self.author_signature = author_signature
self.media_group_id = media_group_id self.media_group_id = media_group_id
self.animation = animation
self.passport_data = passport_data
self.bot = bot self.bot = bot
@ -330,6 +342,7 @@ class Message(TelegramObject):
data['edit_date'] = from_timestamp(data.get('edit_date')) data['edit_date'] = from_timestamp(data.get('edit_date'))
data['audio'] = Audio.de_json(data.get('audio'), bot) data['audio'] = Audio.de_json(data.get('audio'), bot)
data['document'] = Document.de_json(data.get('document'), 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['game'] = Game.de_json(data.get('game'), bot)
data['photo'] = PhotoSize.de_list(data.get('photo'), bot) data['photo'] = PhotoSize.de_list(data.get('photo'), bot)
data['sticker'] = Sticker.de_json(data.get('sticker'), 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['pinned_message'] = Message.de_json(data.get('pinned_message'), bot)
data['invoice'] = Invoice.de_json(data.get('invoice'), bot) data['invoice'] = Invoice.de_json(data.get('invoice'), bot)
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), 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) return cls(bot=bot, **data)
@ -354,6 +368,7 @@ class Message(TelegramObject):
:class:`telegram.Audio` :class:`telegram.Audio`
or :class:`telegram.Contact` or :class:`telegram.Contact`
or :class:`telegram.Document` or :class:`telegram.Document`
or :class:`telegram.Animation`
or :class:`telegram.Game` or :class:`telegram.Game`
or :class:`telegram.Invoice` or :class:`telegram.Invoice`
or :class:`telegram.Location` or :class:`telegram.Location`
@ -551,6 +566,23 @@ class Message(TelegramObject):
self._quote(kwargs) self._quote(kwargs)
return self.bot.send_document(self.chat_id, *args, **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): def reply_sticker(self, *args, **kwargs):
"""Shortcut for:: """Shortcut for::
@ -699,7 +731,7 @@ class Message(TelegramObject):
Note: Note:
You can only edit messages that the bot sent itself, You can only edit messages that the bot sent itself,
therefore this method can only be used on the 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: Returns:
:class:`telegram.Message`: On success, instance representing the edited message. :class:`telegram.Message`: On success, instance representing the edited message.
@ -728,6 +760,27 @@ class Message(TelegramObject):
return self.bot.edit_message_caption( return self.bot.edit_message_caption(
chat_id=self.chat_id, message_id=self.message_id, *args, **kwargs) 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): def edit_reply_markup(self, *args, **kwargs):
"""Shortcut for:: """Shortcut for::

View file

@ -81,6 +81,10 @@ class MessageEntity(TelegramObject):
""":obj:`str`: 'mention'""" """:obj:`str`: 'mention'"""
HASHTAG = 'hashtag' HASHTAG = 'hashtag'
""":obj:`str`: 'hashtag'""" """:obj:`str`: 'hashtag'"""
CASHTAG = 'cashtag'
""":obj:`str`: 'cashtag'"""
PHONE_NUMBER = 'phone_number'
""":obj:`str`: 'phone_number'"""
BOT_COMMAND = 'bot_command' BOT_COMMAND = 'bot_command'
""":obj:`str`: 'bot_command'""" """:obj:`str`: 'bot_command'"""
URL = 'url' URL = 'url'
@ -100,6 +104,7 @@ class MessageEntity(TelegramObject):
TEXT_MENTION = 'text_mention' TEXT_MENTION = 'text_mention'
""":obj:`str`: 'text_mention'""" """:obj:`str`: 'text_mention'"""
ALL_TYPES = [ 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.""" """List[:obj:`str`]: List of all the types."""

View file

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import 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

109
telegram/passport/data.py Normal file
View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
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)

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram 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

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""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

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains 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)

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a 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

View file

@ -203,6 +203,19 @@ class User(TelegramObject):
""" """
return self.bot.send_document(self.id, *args, **kwargs) 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): def send_sticker(self, *args, **kwargs):
"""Shortcut for:: """Shortcut for::

View file

@ -17,11 +17,12 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains methods to make POST and GET requests.""" """This module contains methods to make POST and GET requests."""
import logging
import os import os
import socket import socket
import sys import sys
import logging
import warnings import warnings
from builtins import str # For PY2
try: try:
import ujson as json import ujson as json
@ -40,12 +41,14 @@ except ImportError: # pragma: no cover
"how to properly install.") "how to properly install.")
raise raise
from telegram import (InputFile, TelegramError) from telegram import (InputFile, TelegramError, InputMedia)
from telegram.error import (Unauthorized, NetworkError, TimedOut, BadRequest, ChatMigrated, from telegram.error import (Unauthorized, NetworkError, TimedOut, BadRequest, ChatMigrated,
RetryAfter, InvalidToken) RetryAfter, InvalidToken)
logging.getLogger('urllib3').setLevel(logging.WARNING) logging.getLogger('urllib3').setLevel(logging.WARNING)
USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)'
class Request(object): class Request(object):
""" """
@ -83,9 +86,12 @@ class Request(object):
# TODO: Support other platforms like mac and windows. # TODO: Support other platforms like mac and windows.
if 'linux' in sys.platform: if 'linux' in sys.platform:
sockopts.append((socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 120)) sockopts.append((socket.IPPROTO_TCP,
sockopts.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 30)) socket.TCP_KEEPIDLE, 120)) # pylint: disable=no-member
sockopts.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 8)) 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 self._con_pool_size = con_pool_size
@ -190,6 +196,8 @@ class Request(object):
if 'headers' not in kwargs: if 'headers' not in kwargs:
kwargs['headers'] = {} kwargs['headers'] = {}
kwargs['headers']['connection'] = 'keep-alive' kwargs['headers']['connection'] = 'keep-alive'
# Also set our user agent
kwargs['headers']['user-agent'] = USER_AGENT
try: try:
resp = self._con_pool.request(*args, **kwargs) resp = self._con_pool.request(*args, **kwargs)
@ -264,18 +272,41 @@ class Request(object):
if timeout is not None: if timeout is not None:
urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout) urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout)
if InputFile.is_inputfile(data): # Are we uploading files?
data = InputFile(data) files = False
result = self._request_wrapper(
'POST', url, body=data.to_form(), headers=data.headers, **urlopen_kwargs) 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: else:
data = json.dumps(data) # Attach and set val to attached name for all
result = self._request_wrapper( media = []
'POST', for m in val:
url, media.append(m.to_dict())
body=data.encode(), if isinstance(m.media, InputFile):
headers={'Content-Type': 'application/json'}, data[m.media.attach] = m.media.field_tuple
**urlopen_kwargs) 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:
result = self._request_wrapper('POST', url,
body=json.dumps(data).encode('utf-8'),
headers={'Content-Type': 'application/json'})
return self._parse(result) return self._parse(result)

View file

@ -26,7 +26,7 @@ from time import sleep
import pytest import pytest
from telegram import Bot from telegram import Bot
from telegram.ext import Dispatcher, JobQueue from telegram.ext import Dispatcher, JobQueue, Updater
from tests.bots import get_bot from tests.bots import get_bot
TRAVIS = os.getenv('TRAVIS', False) TRAVIS = os.getenv('TRAVIS', False)
@ -34,6 +34,10 @@ TRAVIS = os.getenv('TRAVIS', False)
if TRAVIS: if TRAVIS:
pytest_plugins = ['tests.travis_fold'] 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') @pytest.fixture(scope='session')
def bot_info(): def bot_info():
@ -42,7 +46,7 @@ def bot_info():
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def bot(bot_info): def bot(bot_info):
return Bot(bot_info['token']) return Bot(bot_info['token'], private_key=PRIVATE_KEY)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
@ -105,6 +109,28 @@ def dp(_dp):
Dispatcher._Dispatcher__singleton_semaphore.release() 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): def pytest_configure(config):
if sys.version_info >= (3,): if sys.version_info >= (3,):
config.addinivalue_line('filterwarnings', 'ignore::ResourceWarning') config.addinivalue_line('filterwarnings', 'ignore::ResourceWarning')

BIN
tests/data/thumb.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -18,58 +18,109 @@
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest import pytest
from flaky import flaky
from telegram import PhotoSize, Animation, Voice from telegram import PhotoSize, Animation, Voice
@pytest.fixture(scope='class') @pytest.fixture(scope='function')
def thumb(): def animation_file():
return PhotoSize(height=50, file_size=1613, file_id='AAQEABPQUWQZAAT7gZuQAAH0bd93VwACAg', f = open('tests/data/game.gif', 'rb')
width=90) yield f
f.close()
@pytest.fixture(scope='class') @pytest.fixture(scope='class')
def animation(thumb, bot): def animation(bot, chat_id):
return Animation(TestAnimation.animation_file_id, thumb=thumb.to_dict(), with open('tests/data/game.gif', 'rb') as f:
file_name=TestAnimation.file_name, mime_type=TestAnimation.mime_type, return bot.send_animation(chat_id, animation=f, timeout=50,
file_size=TestAnimation.file_size, bot=bot) thumb=open('tests/data/thumb.jpg', 'rb')).animation
class TestAnimation(object): class TestAnimation(object):
animation_file_id = 'CgADBAADFQEAAny4rAUgukhiTv2TWwI' animation_file_id = 'CgADAQADngIAAuyVeEez0xRovKi9VAI'
width = 320
height = 180
duration = 1
file_name = 'game.gif.mp4' file_name = 'game.gif.mp4'
mime_type = 'video/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 = { json_dict = {
'file_id': self.animation_file_id, '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, 'file_name': self.file_name,
'mime_type': self.mime_type, 'mime_type': self.mime_type,
'file_size': self.file_size 'file_size': self.file_size
} }
animation = Animation.de_json(json_dict, bot) animation = Animation.de_json(json_dict, bot)
assert animation.file_id == self.animation_file_id 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.file_name == self.file_name
assert animation.mime_type == self.mime_type assert animation.mime_type == self.mime_type
assert animation.file_size == self.file_size 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() animation_dict = animation.to_dict()
assert isinstance(animation_dict, dict) assert isinstance(animation_dict, dict)
assert animation_dict['file_id'] == animation.file_id 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['file_name'] == animation.file_name
assert animation_dict['mime_type'] == animation.mime_type assert animation_dict['mime_type'] == animation.mime_type
assert animation_dict['file_size'] == animation.file_size assert animation_dict['file_size'] == animation.file_size
def test_equality(self): def test_equality(self):
a = Animation(self.animation_file_id) a = Animation(self.animation_file_id, self.height, self.width, self.duration)
b = Animation(self.animation_file_id) b = Animation(self.animation_file_id, self.height, self.width, self.duration)
d = Animation('') d = Animation('', 0, 0, 0)
e = Voice(self.animation_file_id, 0) e = Voice(self.animation_file_id, 0)
assert a == b assert a == b

View file

@ -34,7 +34,8 @@ def audio_file():
@pytest.fixture(scope='class') @pytest.fixture(scope='class')
def audio(bot, chat_id): def audio(bot, chat_id):
with open('tests/data/telegram.mp3', 'rb') as f: 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): class TestAudio(object):
@ -47,6 +48,9 @@ class TestAudio(object):
audio_file_url = 'https://goo.gl/3En24v' audio_file_url = 'https://goo.gl/3En24v'
mime_type = 'audio/mpeg' mime_type = 'audio/mpeg'
file_size = 122920 file_size = 122920
thumb_file_size = 2744
thumb_width = 50
thumb_height = 50
def test_creation(self, audio): def test_creation(self, audio):
# Make sure file has been uploaded. # Make sure file has been uploaded.
@ -60,14 +64,17 @@ class TestAudio(object):
assert audio.title is None assert audio.title is None
assert audio.mime_type == self.mime_type assert audio.mime_type == self.mime_type
assert audio.file_size == self.file_size 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) @flaky(3, 1)
@pytest.mark.timeout(10) @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, message = bot.send_audio(chat_id, audio=audio_file, caption=self.caption,
duration=self.duration, performer=self.performer, duration=self.duration, performer=self.performer,
title=self.title, disable_notification=False, title=self.title, disable_notification=False,
parse_mode='Markdown') parse_mode='Markdown', thumb=thumb_file)
assert message.caption == self.caption.replace('*', '') assert message.caption == self.caption.replace('*', '')
@ -79,6 +86,9 @@ class TestAudio(object):
assert message.audio.title == self.title assert message.audio.title == self.title
assert message.audio.mime_type == self.mime_type assert message.audio.mime_type == self.mime_type
assert message.audio.file_size == self.file_size 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) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
@ -122,14 +132,15 @@ class TestAudio(object):
message = bot.send_audio(audio=audio, chat_id=chat_id) message = bot.send_audio(audio=audio, chat_id=chat_id)
assert message assert message
def test_de_json(self, bot): def test_de_json(self, bot, audio):
json_dict = {'file_id': 'not a file id', json_dict = {'file_id': 'not a file id',
'duration': self.duration, 'duration': self.duration,
'performer': self.performer, 'performer': self.performer,
'title': self.title, 'title': self.title,
'caption': self.caption, 'caption': self.caption,
'mime_type': self.mime_type, '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) json_audio = Audio.de_json(json_dict, bot)
assert json_audio.file_id == 'not a file id' 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.title == self.title
assert json_audio.mime_type == self.mime_type assert json_audio.mime_type == self.mime_type
assert json_audio.file_size == self.file_size assert json_audio.file_size == self.file_size
assert json_audio.thumb == audio.thumb
def test_to_dict(self, audio): def test_to_dict(self, audio):
audio_dict = audio.to_dict() audio_dict = audio.to_dict()

View file

@ -104,8 +104,9 @@ class TestBot(object):
# Considering that the first message is old enough # Considering that the first message is old enough
bot.delete_message(chat_id=chat_id, message_id=1) 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 # 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_media_group and send_animation are tested in their respective test modules. No need to
# duplicate here.
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
@ -114,14 +115,19 @@ class TestBot(object):
latitude = -23.691288 latitude = -23.691288
title = 'title' title = 'title'
address = 'address' address = 'address'
foursquare_id = 'foursquare id'
foursquare_type = 'foursquare type'
message = bot.send_venue(chat_id=chat_id, title=title, address=address, latitude=latitude, 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
assert message.venue.title == title assert message.venue.title == title
assert message.venue.address == address assert message.venue.address == address
assert message.venue.location.latitude == latitude assert message.venue.location.latitude == latitude
assert message.venue.location.longitude == longitude assert message.venue.location.longitude == longitude
assert message.venue.foursquare_id == foursquare_id
assert message.venue.foursquare_type == foursquare_type
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
@ -258,6 +264,8 @@ class TestBot(object):
assert message.caption == 'new_caption' assert message.caption == 'new_caption'
# edit_message_media is tested in test_inputmedia
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
def test_edit_message_caption_with_parse_mode(self, bot, media_message): def test_edit_message_caption_with_parse_mode(self, bot, media_message):

View file

@ -189,6 +189,13 @@ class TestChat(object):
monkeypatch.setattr('telegram.Bot.send_voice', test) monkeypatch.setattr('telegram.Bot.send_voice', test)
assert chat.send_voice('test_voice') 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): def test_equality(self):
a = Chat(self.id, self.title, self.type) a = Chat(self.id, self.title, self.type)
b = Chat(self.id, self.title, self.type) b = Chat(self.id, self.title, self.type)

View file

@ -45,7 +45,7 @@ class TestDocument(object):
file_name = 'telegram.png' file_name = 'telegram.png'
thumb_file_size = 2364 thumb_file_size = 2364
thumb_width = 90 thumb_width = 90
thumb_heigth = 90 thumb_height = 90
def test_creation(self, document): def test_creation(self, document):
assert isinstance(document, Document) assert isinstance(document, Document)
@ -58,14 +58,14 @@ class TestDocument(object):
assert document.file_name == self.file_name assert document.file_name == self.file_name
assert document.thumb.file_size == self.thumb_file_size assert document.thumb.file_size == self.thumb_file_size
assert document.thumb.width == self.thumb_width assert document.thumb.width == self.thumb_width
assert document.thumb.height == self.thumb_heigth assert document.thumb.height == self.thumb_height
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @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, message = bot.send_document(chat_id, document=document_file, caption=self.caption,
disable_notification=False, filename='telegram_custom.png', 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, Document)
assert isinstance(message.document.file_id, str) 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.file_name == 'telegram_custom.png'
assert message.document.mime_type == document.mime_type assert message.document.mime_type == document.mime_type
assert message.document.file_size == document.file_size assert message.document.file_size == document.file_size
assert message.document.thumb == document.thumb
assert message.caption == self.caption.replace('*', '') assert message.caption == self.caption.replace('*', '')
assert message.document.thumb.width == 50
assert message.document.thumb.height == 50
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import 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)

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import 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)

View file

@ -188,6 +188,11 @@ class TestFilters(object):
assert Filters.document.category("application/")(message) assert Filters.document.category("application/")(message)
assert Filters.document.mime_type("application/x-sh")(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): def test_filters_photo(self, message):
assert not Filters.photo(message) assert not Filters.photo(message)
message.photo = 'test' message.photo = 'test'
@ -396,6 +401,11 @@ class TestFilters(object):
message.successful_payment = 'test' message.successful_payment = 'test'
assert Filters.successful_payment(message) 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): def test_language_filter_single(self, message):
message.from_user.language_code = 'en_US' message.from_user.language_code = 'en_US'
assert (Filters.language('en_US'))(message) assert (Filters.language('en_US'))(message)

View file

@ -39,7 +39,7 @@ class TestGame(object):
text = (b'\\U0001f469\\u200d\\U0001f469\\u200d\\U0001f467' text = (b'\\U0001f469\\u200d\\U0001f469\\u200d\\U0001f467'
b'\\u200d\\U0001f467\\U0001f431http://google.com').decode('unicode-escape') b'\\u200d\\U0001f467\\U0001f431http://google.com').decode('unicode-escape')
text_entities = [MessageEntity(13, 17, MessageEntity.URL)] text_entities = [MessageEntity(13, 17, MessageEntity.URL)]
animation = Animation('blah') animation = Animation('blah', 320, 180, 1)
def test_de_json_required(self, bot): def test_de_json_required(self, bot):
json_dict = { json_dict = {

View file

@ -32,6 +32,7 @@ def inline_query_result_venue():
TestInlineQueryResultVenue.title, TestInlineQueryResultVenue.title,
TestInlineQueryResultVenue.address, TestInlineQueryResultVenue.address,
foursquare_id=TestInlineQueryResultVenue.foursquare_id, foursquare_id=TestInlineQueryResultVenue.foursquare_id,
foursquare_type=TestInlineQueryResultVenue.foursquare_type,
thumb_url=TestInlineQueryResultVenue.thumb_url, thumb_url=TestInlineQueryResultVenue.thumb_url,
thumb_width=TestInlineQueryResultVenue.thumb_width, thumb_width=TestInlineQueryResultVenue.thumb_width,
thumb_height=TestInlineQueryResultVenue.thumb_height, thumb_height=TestInlineQueryResultVenue.thumb_height,
@ -47,6 +48,7 @@ class TestInlineQueryResultVenue(object):
title = 'title' title = 'title'
address = 'address' address = 'address'
foursquare_id = 'foursquare id' foursquare_id = 'foursquare id'
foursquare_type = 'foursquare type'
thumb_url = 'thumb url' thumb_url = 'thumb url'
thumb_width = 10 thumb_width = 10
thumb_height = 15 thumb_height = 15
@ -61,6 +63,7 @@ class TestInlineQueryResultVenue(object):
assert inline_query_result_venue.title == self.title assert inline_query_result_venue.title == self.title
assert inline_query_result_venue.address == self.address assert inline_query_result_venue.address == self.address
assert inline_query_result_venue.foursquare_id == self.foursquare_id 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_url == self.thumb_url
assert inline_query_result_venue.thumb_width == self.thumb_width assert inline_query_result_venue.thumb_width == self.thumb_width
assert inline_query_result_venue.thumb_height == self.thumb_height 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['address'] == inline_query_result_venue.address
assert (inline_query_result_venue_dict['foursquare_id'] == assert (inline_query_result_venue_dict['foursquare_id'] ==
inline_query_result_venue.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_url'] == inline_query_result_venue.thumb_url
assert (inline_query_result_venue_dict['thumb_width'] == assert (inline_query_result_venue_dict['thumb_width'] ==
inline_query_result_venue.thumb_width) inline_query_result_venue.thumb_width)

View file

@ -20,6 +20,7 @@
import os import os
import subprocess import subprocess
import sys import sys
from io import BytesIO
from telegram import InputFile from telegram import InputFile
@ -34,7 +35,7 @@ class TestInputFile(object):
cmd = ['cat', self.png] cmd = ['cat', self.png]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=(sys.platform == 'win32')) 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.input_file_content == open(self.png, 'rb').read()
assert in_file.mimetype == 'image/png' 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 # This exception may be thrown if the process has finished before we had the chance
# to kill it. # to kill it.
pass 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'

View file

@ -19,29 +19,69 @@
import pytest import pytest
from flaky import flaky from flaky import flaky
from telegram import InputMediaVideo, InputMediaPhoto, Message from telegram import (InputMediaVideo, InputMediaPhoto, InputMediaAnimation, Message, InputFile,
from .test_video import video, video_file # noqa: F401 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 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') @pytest.fixture(scope='class')
def input_media_video(): def input_media_video(class_thumb_file):
return InputMediaVideo(media=TestInputMediaVideo.media, return InputMediaVideo(media=TestInputMediaVideo.media,
caption=TestInputMediaVideo.caption, caption=TestInputMediaVideo.caption,
width=TestInputMediaVideo.width, width=TestInputMediaVideo.width,
height=TestInputMediaVideo.height, height=TestInputMediaVideo.height,
duration=TestInputMediaVideo.duration, duration=TestInputMediaVideo.duration,
parse_mode=TestInputMediaVideo.parse_mode, parse_mode=TestInputMediaVideo.parse_mode,
thumb=class_thumb_file,
supports_streaming=TestInputMediaVideo.supports_streaming) supports_streaming=TestInputMediaVideo.supports_streaming)
@pytest.fixture(scope='class') @pytest.fixture(scope='class')
def input_media_photo(): def input_media_photo(class_thumb_file):
return InputMediaPhoto(media=TestInputMediaPhoto.media, return InputMediaPhoto(media=TestInputMediaPhoto.media,
caption=TestInputMediaPhoto.caption, caption=TestInputMediaPhoto.caption,
parse_mode=TestInputMediaPhoto.parse_mode) 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): class TestInputMediaVideo(object):
type = "video" type = "video"
media = "NOTAREALFILEID" media = "NOTAREALFILEID"
@ -61,6 +101,7 @@ class TestInputMediaVideo(object):
assert input_media_video.duration == self.duration assert input_media_video.duration == self.duration
assert input_media_video.parse_mode == self.parse_mode assert input_media_video.parse_mode == self.parse_mode
assert input_media_video.supports_streaming == self.supports_streaming assert input_media_video.supports_streaming == self.supports_streaming
assert isinstance(input_media_video.thumb, InputFile)
def test_to_dict(self, input_media_video): def test_to_dict(self, input_media_video):
input_media_video_dict = input_media_video.to_dict() 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.duration == video.duration
assert input_media_video.caption == "test 3" 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 # fixture found in test_video
with pytest.raises(ValueError, match="file_id, url or Video"): input_media_video = InputMediaVideo(video_file, caption="test 3")
InputMediaVideo(video_file) assert input_media_video.type == self.type
assert isinstance(input_media_video.media, InputFile)
assert input_media_video.caption == "test 3"
class TestInputMediaPhoto(object): class TestInputMediaPhoto(object):
@ -110,15 +153,140 @@ class TestInputMediaPhoto(object):
def test_with_photo(self, photo): # noqa: F811 def test_with_photo(self, photo): # noqa: F811
# fixture found in test_photo # fixture found in test_photo
imp = InputMediaPhoto(photo, caption="test 2") input_media_photo = InputMediaPhoto(photo, caption="test 2")
assert imp.type == self.type assert input_media_photo.type == self.type
assert imp.media == photo.file_id assert input_media_photo.media == photo.file_id
assert imp.caption == "test 2" 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 # fixture found in test_photo
with pytest.raises(ValueError, match="file_id, url or PhotoSize"): input_media_photo = InputMediaPhoto(photo_file, caption="test 2")
InputMediaPhoto(photo_file) 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 @pytest.fixture(scope='function') # noqa: F811
@ -148,6 +316,32 @@ class TestSendMediaGroup(object):
assert all([isinstance(mes, Message) for mes in messages]) assert all([isinstance(mes, Message) for mes in messages])
assert all([mes.media_group_id == messages[0].media_group_id 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, bot, chat_id, video_file, photo_file, # noqa: F811
def test_send_media_group_new_files(self): animation_file): # noqa: F811
pass 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)

View file

@ -28,7 +28,8 @@ def input_venue_message_content():
TestInputVenueMessageContent.longitude, TestInputVenueMessageContent.longitude,
TestInputVenueMessageContent.title, TestInputVenueMessageContent.title,
TestInputVenueMessageContent.address, TestInputVenueMessageContent.address,
foursquare_id=TestInputVenueMessageContent.foursquare_id) foursquare_id=TestInputVenueMessageContent.foursquare_id,
foursquare_type=TestInputVenueMessageContent.foursquare_type)
class TestInputVenueMessageContent(object): class TestInputVenueMessageContent(object):
@ -37,6 +38,7 @@ class TestInputVenueMessageContent(object):
title = 'title' title = 'title'
address = 'address' address = 'address'
foursquare_id = 'foursquare id' foursquare_id = 'foursquare id'
foursquare_type = 'foursquare type'
def test_expected_values(self, input_venue_message_content): def test_expected_values(self, input_venue_message_content):
assert input_venue_message_content.longitude == self.longitude 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.title == self.title
assert input_venue_message_content.address == self.address assert input_venue_message_content.address == self.address
assert input_venue_message_content.foursquare_id == self.foursquare_id 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): def test_to_dict(self, input_venue_message_content):
input_venue_message_content_dict = input_venue_message_content.to_dict() 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['address'] == input_venue_message_content.address
assert (input_venue_message_content_dict['foursquare_id'] == assert (input_venue_message_content_dict['foursquare_id'] ==
input_venue_message_content.foursquare_id) input_venue_message_content.foursquare_id)
assert (input_venue_message_content_dict['foursquare_type'] ==
input_venue_message_content.foursquare_type)

View file

@ -112,21 +112,11 @@ class TestInvoice(object):
def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token): def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token):
def test(_, url, data, **kwargs): 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) monkeypatch.setattr('telegram.utils.request.Request.post', test)
assert bot.send_invoice(chat_id, self.title, self.description, self.payload, assert bot.send_invoice(chat_id, self.title, self.description, self.payload,
provider_token, self.start_parameter, self.currency, provider_token, self.start_parameter, self.currency,
self.prices, provider_data={'test_data': 123456789}) 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'})

View file

@ -21,9 +21,10 @@ from datetime import datetime
import pytest import pytest
from telegram import ParseMode 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, 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') @pytest.fixture(scope='class')
@ -51,6 +52,8 @@ def message(bot):
'caption': 'audio_file'}, 'caption': 'audio_file'},
{'document': Document('document_id'), {'document': Document('document_id'),
'caption': 'document_file'}, 'caption': 'document_file'},
{'animation': Animation('animation_id', 30, 30, 1),
'caption': 'animation_file'},
{'game': Game('my_game', 'just my game', {'game': Game('my_game', 'just my game',
[PhotoSize('game_photo_id', 30, 30), ])}, [PhotoSize('game_photo_id', 30, 30), ])},
{'photo': [PhotoSize('photo_id', 50, 50)], {'photo': [PhotoSize('photo_id', 50, 50)],
@ -84,15 +87,17 @@ def message(bot):
{'author_signature': 'some_author_sign'}, {'author_signature': 'some_author_sign'},
{'photo': [PhotoSize('photo_id', 50, 50)], {'photo': [PhotoSize('photo_id', 50, 50)],
'caption': 'photo_file', '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', ids=['forwarded_user', 'forwarded_channel', 'reply', 'edited', 'text',
'caption_entities', 'audio', 'document', 'game', 'photo', 'sticker', 'video', 'caption_entities', 'audio', 'document', 'animation', 'game', 'photo',
'voice', 'video_note', 'new_members', 'contact', 'location', 'venue', 'sticker', 'video', 'voice', 'video_note', 'new_members', 'contact',
'left_member', 'new_title', 'new_photo', 'delete_photo', 'group_created', 'location', 'venue', 'left_member', 'new_title', 'new_photo', 'delete_photo',
'supergroup_created', 'channel_created', 'migrated_to', 'migrated_from', 'group_created', 'supergroup_created', 'channel_created', 'migrated_to',
'pinned', 'invoice', 'successful_payment', 'connected_website', 'migrated_from', 'pinned', 'invoice', 'successful_payment',
'forward_signature', 'author_signature', 'photo_from_media_group']) 'connected_website', 'forward_signature', 'author_signature',
'photo_from_media_group', 'passport_data'])
def message_params(bot, request): def message_params(bot, request):
return Message(message_id=TestMessage.id, return Message(message_id=TestMessage.id,
from_user=TestMessage.from_user, from_user=TestMessage.from_user,
@ -295,8 +300,9 @@ class TestMessage(object):
assert message.link is None assert message.link is None
def test_effective_attachment(self, message_params): def test_effective_attachment(self, message_params):
for i in ('audio', 'game', 'document', 'photo', 'sticker', 'video', 'voice', 'video_note', for i in ('audio', 'game', 'document', 'animation', 'photo', 'sticker', 'video', 'voice',
'contact', 'location', 'venue', 'invoice', 'invoice', 'successful_payment'): 'video_note', 'contact', 'location', 'venue', 'invoice', 'invoice',
'successful_payment'):
item = getattr(message_params, i, None) item = getattr(message_params, i, None)
if item: if item:
break break
@ -424,6 +430,20 @@ class TestMessage(object):
assert message.reply_document(document='test_document') assert message.reply_document(document='test_document')
assert message.reply_document(document='test_document', quote=True) 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_reply_sticker(self, monkeypatch, message):
def test(*args, **kwargs): def test(*args, **kwargs):
id = args[1] == message.chat_id id = args[1] == message.chat_id
@ -558,6 +578,16 @@ class TestMessage(object):
monkeypatch.setattr('telegram.Bot.edit_message_caption', test) monkeypatch.setattr('telegram.Bot.edit_message_caption', test)
assert message.edit_caption(caption='new caption') 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_edit_reply_markup(self, monkeypatch, message):
def test(*args, **kwargs): def test(*args, **kwargs):
chat_id = kwargs['chat_id'] == message.chat_id chat_id = kwargs['chat_id'] == message.chat_id

View file

@ -23,8 +23,8 @@ from platform import python_implementation
import certifi import certifi
import pytest import pytest
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from telegram.vendor.ptb_urllib3 import urllib3 from telegram.vendor.ptb_urllib3 import urllib3
import telegram import telegram
IGNORED_OBJECTS = ('ResponseParameters', 'CallbackGame') IGNORED_OBJECTS = ('ResponseParameters', 'CallbackGame')
@ -99,6 +99,8 @@ def check_object(h4):
elif ((name.startswith('InlineQueryResult') or elif ((name.startswith('InlineQueryResult') or
name.startswith('InputMedia')) and field == 'type'): name.startswith('InputMedia')) and field == 'type'):
continue continue
elif name.startswith('PassportElementError') and field == 'source':
continue
elif field == 'remove_keyboard': elif field == 'remove_keyboard':
continue continue
@ -110,14 +112,15 @@ def check_object(h4):
ignored = IGNORED_PARAMETERS.copy() ignored = IGNORED_PARAMETERS.copy()
if name == 'InputFile': if name == 'InputFile':
ignored |= {'data'} return
elif name == 'InlineQueryResult': elif name == 'InlineQueryResult':
ignored |= {'id'} ignored |= {'id', 'type'}
elif name == 'User': elif name == 'User':
ignored |= {'type'} # TODO: Deprecation ignored |= {'type'} # TODO: Deprecation
elif name in ('PassportFile', 'EncryptedPassportElement'):
if name.startswith('InlineQueryResult'): ignored |= {'credentials'}
ignored |= {'type'} elif name == 'PassportElementError':
ignored |= {'message', 'type', 'source'}
assert (sig.parameters.keys() ^ checked) - ignored == set() assert (sig.parameters.keys() ^ checked) - ignored == set()

290
tests/test_passport.py Normal file
View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
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)

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import 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)

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import 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)

View file

@ -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 <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import 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)

Some files were not shown because too many files have changed in this diff Show more