README.md edited

This commit is contained in:
2024-12-06 10:45:08 +09:00
parent 09e4edee6b
commit 1aa387aa59
13921 changed files with 2057290 additions and 10 deletions

View File

@@ -0,0 +1,537 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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/].
# pylint: disable=missing-module-docstring, redefined-builtin
import json
from base64 import b64decode
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional, no_type_check
try:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.padding import MGF1, OAEP
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 SHA1, SHA256, SHA512, Hash
CRYPTO_INSTALLED = True
except ImportError:
default_backend = None # type: ignore[assignment]
MGF1, OAEP, Cipher, AES, CBC = (None, None, None, None, None) # type: ignore[misc,assignment]
SHA1, SHA256, SHA512, Hash = (None, None, None, None) # type: ignore[misc,assignment]
CRYPTO_INSTALLED = False
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.strings import TextEncoding
from telegram._utils.types import JSONDict
from telegram.error import PassportDecryptionError
if TYPE_CHECKING:
from telegram import Bot
@no_type_check
def decrypt(secret, hash, data):
"""
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:`PassportDecryptionError`: Given hash does not match hash of decrypted data.
Returns:
:obj:`bytes`: The decrypted data as bytes.
"""
if not CRYPTO_INSTALLED:
raise RuntimeError(
"To use Telegram Passports, PTB must be installed via `pip install "
'"python-telegram-bot[passport]"`.'
)
# 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, init_vector = secret_hash_hash[:32], secret_hash_hash[32 : 32 + 16]
# Init a AES-CBC cipher and decrypt the data
cipher = Cipher(AES(key), CBC(init_vector), 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 PassportDecryptionError(f"Hashes are not equal! {data_hash} != {hash}")
# Return data without padding
return data[data[0] :]
@no_type_check
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(TextEncoding.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.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`data`, :attr:`hash` and :attr:`secret` are equal.
Note:
This object is decrypted only when originating from
:attr:`telegram.PassportData.decrypted_credentials`.
Args:
data (:class:`telegram.Credentials` | :obj:`str`): Decrypted data with unique user's
nonce, 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.
Attributes:
data (:class:`telegram.Credentials` | :obj:`str`): Decrypted data with unique user's
nonce, 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.
"""
__slots__ = (
"_decrypted_data",
"_decrypted_secret",
"data",
"hash",
"secret",
)
def __init__(
self,
data: str,
hash: str,
secret: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.data: str = data
self.hash: str = hash
self.secret: str = secret
self._id_attrs = (self.data, self.hash, self.secret)
self._decrypted_secret: Optional[bytes] = None
self._decrypted_data: Optional[Credentials] = None
self._freeze()
@property
def decrypted_secret(self) -> bytes:
"""
:obj:`bytes`: Lazily decrypt and return secret.
Raises:
telegram.error.PassportDecryptionError: Decryption failed. Usually due to bad
private/public key but can also suggest malformed/tampered data.
"""
if self._decrypted_secret is None:
if not CRYPTO_INSTALLED:
raise RuntimeError(
"To use Telegram Passports, PTB must be installed via `pip install "
'"python-telegram-bot[passport]"`.'
)
# 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.get_bot().private_key.decrypt( # type: ignore
b64decode(self.secret),
OAEP(mgf=MGF1(algorithm=SHA1()), algorithm=SHA1(), label=None), # skipcq
)
except ValueError as exception:
# If decryption fails raise exception
raise PassportDecryptionError(exception) from exception
return self._decrypted_secret
@property
def decrypted_data(self) -> "Credentials":
"""
:class:`telegram.Credentials`: Lazily decrypt and return credentials data. This object
also contains the user specified nonce as
`decrypted_data.nonce`.
Raises:
telegram.error.PassportDecryptionError: 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, b64decode(self.hash), b64decode(self.data)),
self.get_bot(),
)
return self._decrypted_data # type: ignore[return-value]
class Credentials(TelegramObject):
"""
Attributes:
secure_data (:class:`telegram.SecureData`): Credentials for encrypted data
nonce (:obj:`str`): Bot-specified nonce
"""
__slots__ = ("nonce", "secure_data")
def __init__(
self,
secure_data: "SecureData",
nonce: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.secure_data: SecureData = secure_data
self.nonce: str = nonce
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["Credentials"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["secure_data"] = SecureData.de_json(data.get("secure_data"), bot=bot)
return super().de_json(data=data, bot=bot)
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.
Args:
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.
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.
"""
__slots__ = (
"address",
"bank_statement",
"driver_license",
"identity_card",
"internal_passport",
"passport",
"passport_registration",
"personal_details",
"rental_agreement",
"temporary_registration",
"utility_bill",
)
def __init__(
self,
personal_details: Optional["SecureValue"] = None,
passport: Optional["SecureValue"] = None,
internal_passport: Optional["SecureValue"] = None,
driver_license: Optional["SecureValue"] = None,
identity_card: Optional["SecureValue"] = None,
address: Optional["SecureValue"] = None,
utility_bill: Optional["SecureValue"] = None,
bank_statement: Optional["SecureValue"] = None,
rental_agreement: Optional["SecureValue"] = None,
passport_registration: Optional["SecureValue"] = None,
temporary_registration: Optional["SecureValue"] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Optionals
self.temporary_registration: Optional[SecureValue] = temporary_registration
self.passport_registration: Optional[SecureValue] = passport_registration
self.rental_agreement: Optional[SecureValue] = rental_agreement
self.bank_statement: Optional[SecureValue] = bank_statement
self.utility_bill: Optional[SecureValue] = utility_bill
self.address: Optional[SecureValue] = address
self.identity_card: Optional[SecureValue] = identity_card
self.driver_license: Optional[SecureValue] = driver_license
self.internal_passport: Optional[SecureValue] = internal_passport
self.passport: Optional[SecureValue] = passport
self.personal_details: Optional[SecureValue] = personal_details
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["SecureData"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
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 super().de_json(data=data, bot=bot)
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.
Args:
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".
translation (list[:class:`telegram.FileCredentials`], optional): Credentials for an
encrypted translation of the document. Available for "passport", "driver_license",
"identity_card", "internal_passport", "utility_bill", "bank_statement",
"rental_agreement", "passport_registration" and "temporary_registration".
files (list[:class:`telegram.FileCredentials`], optional): Credentials for encrypted
files. Available for "utility_bill", "bank_statement", "rental_agreement",
"passport_registration" and "temporary_registration" types.
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".
translation (tuple[:class:`telegram.FileCredentials`]): Optional. Credentials for an
encrypted translation of the document. Available for "passport", "driver_license",
"identity_card", "internal_passport", "utility_bill", "bank_statement",
"rental_agreement", "passport_registration" and "temporary_registration".
.. versionchanged:: 20.0
|tupleclassattrs|
files (tuple[:class:`telegram.FileCredentials`]): Optional. Credentials for encrypted
files. Available for "utility_bill", "bank_statement", "rental_agreement",
"passport_registration" and "temporary_registration" types.
.. versionchanged:: 20.0
* |tupleclassattrs|
* |alwaystuple|
"""
__slots__ = ("data", "files", "front_side", "reverse_side", "selfie", "translation")
def __init__(
self,
data: Optional["DataCredentials"] = None,
front_side: Optional["FileCredentials"] = None,
reverse_side: Optional["FileCredentials"] = None,
selfie: Optional["FileCredentials"] = None,
files: Optional[Sequence["FileCredentials"]] = None,
translation: Optional[Sequence["FileCredentials"]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.data: Optional[DataCredentials] = data
self.front_side: Optional[FileCredentials] = front_side
self.reverse_side: Optional[FileCredentials] = reverse_side
self.selfie: Optional[FileCredentials] = selfie
self.files: tuple[FileCredentials, ...] = parse_sequence_arg(files)
self.translation: tuple[FileCredentials, ...] = parse_sequence_arg(translation)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["SecureValue"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
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)
data["translation"] = FileCredentials.de_list(data.get("translation"), bot=bot)
return super().de_json(data=data, bot=bot)
class _CredentialsBase(TelegramObject):
"""Base class for DataCredentials and FileCredentials."""
__slots__ = ("data_hash", "file_hash", "hash", "secret")
def __init__(
self,
hash: str,
secret: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
with self._unfrozen():
self.hash: str = hash
self.secret: str = secret
# Aliases just to be sure
self.file_hash: str = self.hash
self.data_hash: str = self.hash
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
"""
__slots__ = ()
def __init__(self, data_hash: str, secret: str, *, api_kwargs: Optional[JSONDict] = None):
super().__init__(hash=data_hash, secret=secret, api_kwargs=api_kwargs)
self._freeze()
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
"""
__slots__ = ()
def __init__(self, file_hash: str, secret: str, *, api_kwargs: Optional[JSONDict] = None):
super().__init__(hash=file_hash, secret=secret, api_kwargs=api_kwargs)
self._freeze()

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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/].
# pylint: disable=missing-module-docstring
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class PersonalDetails(TelegramObject):
"""
This object represents personal details.
Args:
first_name (:obj:`str`): First Name.
middle_name (:obj:`str`): Optional. 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).
first_name_native (:obj:`str`): First Name in the language of the user's country of
residence.
middle_name_native (:obj:`str`): Optional. Middle Name in the language of the user's
country of residence.
last_name_native (:obj:`str`): Last Name in the language of the user's country of
residence.
Attributes:
first_name (:obj:`str`): First Name.
middle_name (:obj:`str`): Optional. 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).
first_name_native (:obj:`str`): First Name in the language of the user's country of
residence.
middle_name_native (:obj:`str`): Optional. Middle Name in the language of the user's
country of residence.
last_name_native (:obj:`str`): Last Name in the language of the user's country of
residence.
"""
__slots__ = (
"birth_date",
"country_code",
"first_name",
"first_name_native",
"gender",
"last_name",
"last_name_native",
"middle_name",
"middle_name_native",
"residence_country_code",
)
def __init__(
self,
first_name: str,
last_name: str,
birth_date: str,
gender: str,
country_code: str,
residence_country_code: str,
first_name_native: Optional[str] = None,
last_name_native: Optional[str] = None,
middle_name: Optional[str] = None,
middle_name_native: Optional[str] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.first_name: str = first_name
self.last_name: str = last_name
self.middle_name: Optional[str] = middle_name
self.birth_date: str = birth_date
self.gender: str = gender
self.country_code: str = country_code
self.residence_country_code: str = residence_country_code
self.first_name_native: Optional[str] = first_name_native
self.last_name_native: Optional[str] = last_name_native
self.middle_name_native: Optional[str] = middle_name_native
self._freeze()
class ResidentialAddress(TelegramObject):
"""
This object represents a residential address.
Args:
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.
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.
"""
__slots__ = (
"city",
"country_code",
"post_code",
"state",
"street_line1",
"street_line2",
)
def __init__(
self,
street_line1: str,
street_line2: str,
city: str,
state: str,
country_code: str,
post_code: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.street_line1: str = street_line1
self.street_line2: str = street_line2
self.city: str = city
self.state: str = state
self.country_code: str = country_code
self.post_code: str = post_code
self._freeze()
class IdDocumentData(TelegramObject):
"""
This object represents the data of an identity document.
Args:
document_no (:obj:`str`): Document number.
expiry_date (:obj:`str`): Optional. Date of expiry, in DD.MM.YYYY format.
Attributes:
document_no (:obj:`str`): Document number.
expiry_date (:obj:`str`): Optional. Date of expiry, in DD.MM.YYYY format.
"""
__slots__ = ("document_no", "expiry_date")
def __init__(
self,
document_no: str,
expiry_date: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.document_no: str = document_no
self.expiry_date: str = expiry_date
self._freeze()

View File

@@ -0,0 +1,282 @@
#!/usr/bin/env python
# flake8: noqa: E501
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 base64 import b64decode
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional, Union
from telegram._passport.credentials import decrypt_json
from telegram._passport.data import IdDocumentData, PersonalDetails, ResidentialAddress
from telegram._passport.passportfile import PassportFile
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot, Credentials
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.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type`, :attr:`data`, :attr:`phone_number`, :attr:`email`,
:attr:`files`, :attr:`front_side`, :attr:`reverse_side` and :attr:`selfie` are equal.
Note:
This object is decrypted only when originating from
:attr:`telegram.PassportData.decrypted_data`.
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".
hash (:obj:`str`): Base64-encoded element hash for using in
:class:`telegram.PassportElementErrorUnspecified`.
data (:class:`telegram.PersonalDetails` | :class:`telegram.IdDocumentData` | \
:class:`telegram.ResidentialAddress` | :obj:`str`, optional):
Decrypted or encrypted data; available only for "personal_details", "passport",
"driver_license", "identity_card", "internal_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 (Sequence[:class:`telegram.PassportFile`], optional): Array of encrypted/decrypted
files with documents provided by the user; available only for "utility_bill",
"bank_statement", "rental_agreement", "passport_registration" and
"temporary_registration" types.
.. versionchanged:: 20.0
|sequenceclassargs|
front_side (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the
front side of the document, provided by the user; Available only for "passport",
"driver_license", "identity_card" and "internal_passport".
reverse_side (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the
reverse side of the document, provided by the user; Available only for
"driver_license" and "identity_card".
selfie (:class:`telegram.PassportFile`, optional): Encrypted/decrypted file with the
selfie of the user holding a document, provided by the user; available if requested for
"passport", "driver_license", "identity_card" and "internal_passport".
translation (Sequence[:class:`telegram.PassportFile`], optional): Array of
encrypted/decrypted files with translated versions of documents provided by the user;
available if requested requested for "passport", "driver_license", "identity_card",
"internal_passport", "utility_bill", "bank_statement", "rental_agreement",
"passport_registration" and "temporary_registration" types.
.. versionchanged:: 20.0
|sequenceclassargs|
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".
hash (:obj:`str`): Base64-encoded element hash for using in
:class:`telegram.PassportElementErrorUnspecified`.
data (:class:`telegram.PersonalDetails` | :class:`telegram.IdDocumentData` | \
:class:`telegram.ResidentialAddress` | :obj:`str`):
Optional. Decrypted or encrypted data; available only for "personal_details",
"passport", "driver_license", "identity_card", "internal_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 (tuple[:class:`telegram.PassportFile`]): Optional. Array of encrypted/decrypted
files with documents provided by the user; available only for "utility_bill",
"bank_statement", "rental_agreement", "passport_registration" and
"temporary_registration" types.
.. versionchanged:: 20.0
* |tupleclassattrs|
* |alwaystuple|
front_side (:class:`telegram.PassportFile`): Optional. Encrypted/decrypted file with the
front side of the document, provided by the user; available only for "passport",
"driver_license", "identity_card" and "internal_passport".
reverse_side (:class:`telegram.PassportFile`): Optional. Encrypted/decrypted file with the
reverse side of the document, provided by the user; available only for "driver_license"
and "identity_card".
selfie (:class:`telegram.PassportFile`): Optional. Encrypted/decrypted file with the
selfie of the user holding a document, provided by the user; available if requested for
"passport", "driver_license", "identity_card" and "internal_passport".
translation (tuple[:class:`telegram.PassportFile`]): Optional. Array of
encrypted/decrypted files with translated versions of documents provided by the user;
available if requested for "passport", "driver_license", "identity_card",
"internal_passport", "utility_bill", "bank_statement", "rental_agreement",
"passport_registration" and "temporary_registration" types.
.. versionchanged:: 20.0
* |tupleclassattrs|
* |alwaystuple|
"""
__slots__ = (
"data",
"email",
"files",
"front_side",
"hash",
"phone_number",
"reverse_side",
"selfie",
"translation",
"type",
)
def __init__(
self,
type: str, # pylint: disable=redefined-builtin
hash: str, # pylint: disable=redefined-builtin
data: Optional[Union[PersonalDetails, IdDocumentData, ResidentialAddress]] = None,
phone_number: Optional[str] = None,
email: Optional[str] = None,
files: Optional[Sequence[PassportFile]] = None,
front_side: Optional[PassportFile] = None,
reverse_side: Optional[PassportFile] = None,
selfie: Optional[PassportFile] = None,
translation: Optional[Sequence[PassportFile]] = None,
# TODO: Remove the credentials argument in 22.0 or later
credentials: Optional[ # pylint: disable=unused-argument # noqa: ARG002
"Credentials"
] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.type: str = type
# Optionals
self.data: Optional[Union[PersonalDetails, IdDocumentData, ResidentialAddress]] = data
self.phone_number: Optional[str] = phone_number
self.email: Optional[str] = email
self.files: tuple[PassportFile, ...] = parse_sequence_arg(files)
self.front_side: Optional[PassportFile] = front_side
self.reverse_side: Optional[PassportFile] = reverse_side
self.selfie: Optional[PassportFile] = selfie
self.translation: tuple[PassportFile, ...] = parse_sequence_arg(translation)
self.hash: str = hash
self._id_attrs = (
self.type,
self.data,
self.phone_number,
self.email,
self.files,
self.front_side,
self.reverse_side,
self.selfie,
)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["EncryptedPassportElement"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
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)
data["translation"] = PassportFile.de_list(data.get("translation"), bot) or None
return super().de_json(data=data, bot=bot)
@classmethod
def de_json_decrypted(
cls, data: Optional[JSONDict], bot: Optional["Bot"], credentials: "Credentials"
) -> Optional["EncryptedPassportElement"]:
"""Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account
passport credentials.
Args:
data (dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot` | :obj:`None`): The bot associated with these object.
May be :obj:`None`, in which case shortcut methods will not be available.
.. versionchanged:: 21.4
:paramref:`bot` is now optional and defaults to :obj:`None`
.. deprecated:: 21.4
This argument will be converted to an optional argument in future versions.
credentials (:class:`telegram.FileCredentials`): The credentials
Returns:
:class:`telegram.EncryptedPassportElement`:
"""
if not data:
return None
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(
b64decode(secure_data.data.secret),
b64decode(secure_data.data.hash),
b64decode(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.files) 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
)
data["translation"] = (
PassportFile.de_list_decrypted(
data.get("translation"), bot, secure_data.translation
)
or None
)
return super().de_json(data=data, bot=bot)

View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._passport.credentials import EncryptedCredentials
from telegram._passport.encryptedpassportelement import EncryptedPassportElement
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot, Credentials
class PassportData(TelegramObject):
"""Contains information about Telegram Passport data shared with the bot by the user.
Note:
To be able to decrypt this object, you must pass your ``private_key`` to either
:class:`telegram.ext.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.nonce`.
Args:
data (Sequence[:class:`telegram.EncryptedPassportElement`]): Array with encrypted
information about documents and other Telegram Passport elements that was shared with
the bot.
.. versionchanged:: 20.0
|sequenceclassargs|
credentials (:class:`telegram.EncryptedCredentials`)): Encrypted credentials.
Attributes:
data (tuple[:class:`telegram.EncryptedPassportElement`]): Array with encrypted
information about documents and other Telegram Passport elements that was shared with
the bot.
.. versionchanged:: 20.0
|tupleclassattrs|
credentials (:class:`telegram.EncryptedCredentials`): Encrypted credentials.
"""
__slots__ = ("_decrypted_data", "credentials", "data")
def __init__(
self,
data: Sequence[EncryptedPassportElement],
credentials: EncryptedCredentials,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.data: tuple[EncryptedPassportElement, ...] = parse_sequence_arg(data)
self.credentials: EncryptedCredentials = credentials
self._decrypted_data: Optional[tuple[EncryptedPassportElement]] = None
self._id_attrs = tuple([x.type for x in data] + [credentials.hash])
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PassportData"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["data"] = EncryptedPassportElement.de_list(data.get("data"), bot)
data["credentials"] = EncryptedCredentials.de_json(data.get("credentials"), bot)
return super().de_json(data=data, bot=bot)
@property
def decrypted_data(self) -> tuple[EncryptedPassportElement, ...]:
"""
tuple[:class:`telegram.EncryptedPassportElement`]: Lazily decrypt and return information
about documents and other Telegram Passport elements which were shared with the bot.
.. versionchanged:: 20.0
Returns a tuple instead of a list.
Raises:
telegram.error.PassportDecryptionError: 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 = tuple( # type: ignore[assignment]
EncryptedPassportElement.de_json_decrypted(
element.to_dict(), self.get_bot(), self.decrypted_credentials
)
for element in self.data
)
return self._decrypted_data # type: ignore[return-value]
@property
def decrypted_credentials(self) -> "Credentials":
"""
: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.error.PassportDecryptionError: 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,474 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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/].
# pylint: disable=redefined-builtin
"""This module contains the classes that represent Telegram PassportElementError."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
class PassportElementError(TelegramObject):
"""Baseclass for the PassportElementError* classes.
This object represents an error in the Telegram Passport element which was submitted that
should be resolved by the user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`source` and :attr:`type` are equal.
Args:
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.
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.
"""
__slots__ = ("message", "source", "type")
def __init__(
self, source: str, type: str, message: str, *, api_kwargs: Optional[JSONDict] = None
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.source: str = str(source)
self.type: str = str(type)
self.message: str = str(message)
self._id_attrs = (self.source, self.type)
self._freeze()
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.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.PassportElementError.source`, :attr:`type`,
:attr:`field_name`, :attr:`data_hash` and :attr:`message` are equal.
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.
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.
"""
__slots__ = ("data_hash", "field_name")
def __init__(
self,
type: str,
field_name: str,
data_hash: str,
message: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
# Required
super().__init__("data", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self.field_name: str = field_name
self.data_hash: str = 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.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.PassportElementError.source`, :attr:`type`,
:attr:`file_hash`, and :attr:`message` are equal.
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.
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.
"""
__slots__ = ("file_hash",)
def __init__(
self, type: str, file_hash: str, message: str, *, api_kwargs: Optional[JSONDict] = None
):
# Required
super().__init__("file", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self.file_hash: str = 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 list of
files with the document scans changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.PassportElementError.source`, :attr:`type`,
:attr:`file_hashes`, and :attr:`message` are equal.
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.
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"``.
message (:obj:`str`): Error message.
"""
__slots__ = ("_file_hashes",)
def __init__(
self,
type: str,
file_hashes: list[str],
message: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
# Required
super().__init__("files", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self._file_hashes: list[str] = file_hashes
self._id_attrs = (self.source, self.type, self.message, *tuple(file_hashes))
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict` for details."""
data = super().to_dict(recursive)
data["file_hashes"] = self._file_hashes
return data
@property
def file_hashes(self) -> list[str]:
"""List of base64-encoded file hashes.
.. deprecated:: 20.6
This attribute will return a tuple instead of a list in future major versions.
"""
warn(
PTBDeprecationWarning(
"20.6",
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions.",
),
stacklevel=2,
)
return self._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.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.PassportElementError.source`, :attr:`type`,
:attr:`file_hash`, and :attr:`message` are equal.
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.
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.
"""
__slots__ = ("file_hash",)
def __init__(
self, type: str, file_hash: str, message: str, *, api_kwargs: Optional[JSONDict] = None
):
# Required
super().__init__("front_side", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self.file_hash: str = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message)
class PassportElementErrorReverseSide(PassportElementError):
"""
Represents an issue with the reverse side of a document. The error is considered resolved when
the file with the reverse side of the document changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.PassportElementError.source`, :attr:`type`,
:attr:`file_hash`, and :attr:`message` are equal.
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.
Attributes:
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.
"""
__slots__ = ("file_hash",)
def __init__(
self, type: str, file_hash: str, message: str, *, api_kwargs: Optional[JSONDict] = None
):
# Required
super().__init__("reverse_side", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self.file_hash: str = 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.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.PassportElementError.source`, :attr:`type`,
:attr:`file_hash`, and :attr:`message` are equal.
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.
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.
"""
__slots__ = ("file_hash",)
def __init__(
self, type: str, file_hash: str, message: str, *, api_kwargs: Optional[JSONDict] = None
):
# Required
super().__init__("selfie", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self.file_hash: str = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message)
class PassportElementErrorTranslationFile(PassportElementError):
"""
Represents an issue with one of the files that constitute the translation of a document.
The error is considered resolved when the file changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.PassportElementError.source`, :attr:`type`,
:attr:`file_hash`, and :attr:`message` are equal.
Args:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of ``"passport"``, ``"driver_license"``, ``"identity_card"``,
``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``,
``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``.
file_hash (:obj:`str`): Base64-encoded hash of the file.
message (:obj:`str`): Error message.
Attributes:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of ``"passport"``, ``"driver_license"``, ``"identity_card"``,
``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``,
``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``.
file_hash (:obj:`str`): Base64-encoded hash of the file.
message (:obj:`str`): Error message.
"""
__slots__ = ("file_hash",)
def __init__(
self, type: str, file_hash: str, message: str, *, api_kwargs: Optional[JSONDict] = None
):
# Required
super().__init__("translation_file", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self.file_hash: str = file_hash
self._id_attrs = (self.source, self.type, self.file_hash, self.message)
class PassportElementErrorTranslationFiles(PassportElementError):
"""
Represents an issue with the translated version of a document. The error is considered
resolved when a file with the document translation changes.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.PassportElementError.source`, :attr:`type`,
:attr:`file_hashes`, and :attr:`message` are equal.
Args:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of ``"passport"``, ``"driver_license"``, ``"identity_card"``,
``"internal_passport"``, ``"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.
Attributes:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue,
one of ``"passport"``, ``"driver_license"``, ``"identity_card"``,
``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``,
``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``.
message (:obj:`str`): Error message.
"""
__slots__ = ("_file_hashes",)
def __init__(
self,
type: str,
file_hashes: list[str],
message: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
# Required
super().__init__("translation_files", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self._file_hashes: list[str] = file_hashes
self._id_attrs = (self.source, self.type, self.message, *tuple(file_hashes))
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict` for details."""
data = super().to_dict(recursive)
data["file_hashes"] = self._file_hashes
return data
@property
def file_hashes(self) -> list[str]:
"""List of base64-encoded file hashes.
.. deprecated:: 20.6
This attribute will return a tuple instead of a list in future major versions.
"""
warn(
PTBDeprecationWarning(
"20.6",
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions. See the stability policy:"
" https://docs.python-telegram-bot.org/en/stable/stability_policy.html",
),
stacklevel=2,
)
return self._file_hashes
class PassportElementErrorUnspecified(PassportElementError):
"""
Represents an issue in an unspecified place. The error is considered resolved when new
data is added.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`~telegram.PassportElementError.source`, :attr:`type`,
:attr:`element_hash`, and :attr:`message` are equal.
Args:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue.
element_hash (:obj:`str`): Base64-encoded element hash.
message (:obj:`str`): Error message.
Attributes:
type (:obj:`str`): Type of element of the user's Telegram Passport which has the issue.
element_hash (:obj:`str`): Base64-encoded element hash.
message (:obj:`str`): Error message.
"""
__slots__ = ("element_hash",)
def __init__(
self, type: str, element_hash: str, message: str, *, api_kwargs: Optional[JSONDict] = None
):
# Required
super().__init__("unspecified", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self.element_hash: str = element_hash
self._id_attrs = (self.source, self.type, self.element_hash, self.message)

View File

@@ -0,0 +1,227 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import Bot, File, FileCredentials
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.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`file_unique_id` is equal.
Args:
file_id (:obj:`str`): Identifier for this file, which can be used to download
or reuse the file.
file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file.
file_size (:obj:`int`): File size in bytes.
file_date (:obj:`int`): Unix time when the file was uploaded.
.. deprecated:: 20.6
This argument will only accept a datetime instead of an integer in future
major versions.
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
or reuse the file.
file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file.
file_size (:obj:`int`): File size in bytes.
"""
__slots__ = (
"_credentials",
"_file_date",
"file_id",
"file_size",
"file_unique_id",
)
def __init__(
self,
file_id: str,
file_unique_id: str,
file_date: int,
file_size: int,
credentials: Optional["FileCredentials"] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.file_id: str = file_id
self.file_unique_id: str = file_unique_id
self.file_size: int = file_size
self._file_date: int = file_date
# Optionals
self._credentials: Optional[FileCredentials] = credentials
self._id_attrs = (self.file_unique_id,)
self._freeze()
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict` for details."""
data = super().to_dict(recursive)
data["file_date"] = self._file_date
return data
@property
def file_date(self) -> int:
""":obj:`int`: Unix time when the file was uploaded.
.. deprecated:: 20.6
This attribute will return a datetime instead of a integer in future major versions.
"""
warn(
PTBDeprecationWarning(
"20.6",
"The attribute `file_date` will return a datetime instead of an integer in future"
" major versions.",
),
stacklevel=2,
)
return self._file_date
@classmethod
def de_json_decrypted(
cls, data: Optional[JSONDict], bot: Optional["Bot"], credentials: "FileCredentials"
) -> Optional["PassportFile"]:
"""Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account
passport credentials.
Args:
data (dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot` | :obj:`None`): The bot associated with these object.
May be :obj:`None`, in which case shortcut methods will not be available.
.. versionchanged:: 21.4
:paramref:`bot` is now optional and defaults to :obj:`None`
.. deprecated:: 21.4
This argument will be converted to an optional argument in future versions.
credentials (:class:`telegram.FileCredentials`): The credentials
Returns:
:class:`telegram.PassportFile`:
"""
data = cls._parse_data(data)
if not data:
return None
data["credentials"] = credentials
return super().de_json(data=data, bot=bot)
@classmethod
def de_list_decrypted(
cls,
data: Optional[list[JSONDict]],
bot: Optional["Bot"],
credentials: list["FileCredentials"],
) -> tuple[Optional["PassportFile"], ...]:
"""Variant of :meth:`telegram.TelegramObject.de_list` that also takes into account
passport credentials.
.. versionchanged:: 20.0
* Returns a tuple instead of a list.
* Filters out any :obj:`None` values
Args:
data (list[dict[:obj:`str`, ...]]): The JSON data.
bot (:class:`telegram.Bot` | :obj:`None`): The bot associated with these object.
May be :obj:`None`, in which case shortcut methods will not be available.
.. versionchanged:: 21.4
:paramref:`bot` is now optional and defaults to :obj:`None`
.. deprecated:: 21.4
This argument will be converted to an optional argument in future versions.
credentials (:class:`telegram.FileCredentials`): The credentials
Returns:
tuple[:class:`telegram.PassportFile`]:
"""
if not data:
return ()
return tuple(
obj
for obj in (
cls.de_json_decrypted(passport_file, bot, credentials[i])
for i, passport_file in enumerate(data)
)
if obj is not None
)
async def get_file(
self,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> "File":
"""
Wrapper over :meth:`telegram.Bot.get_file`. Will automatically assign the correct
credentials to the returned :class:`telegram.File` if originating from
:attr:`telegram.PassportData.decrypted_data`.
For the documentation of the arguments, please see :meth:`telegram.Bot.get_file`.
Returns:
:class:`telegram.File`
Raises:
:class:`telegram.error.TelegramError`
"""
file = await self.get_bot().get_file(
file_id=self.file_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
if self._credentials:
file.set_credentials(self._credentials)
return file