This commit is contained in:
2026-09-18 21:33:12 -03:00
parent 66449003d3
commit e129991fcd
202 changed files with 3 additions and 42362 deletions
@@ -1,537 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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()
@@ -1,186 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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()
@@ -1,282 +0,0 @@
#!/usr/bin/env python
# flake8: noqa: E501
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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)
@@ -1,132 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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
@@ -1,474 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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)
@@ -1,227 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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
@@ -1,134 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 Invoice."""
from typing import Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class Invoice(TelegramObject):
"""This object contains basic information about an invoice.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`title`, :attr:`description`, :paramref:`start_parameter`,
:attr:`currency` and :attr:`total_amount` are equal.
Args:
title (:obj:`str`): Product name.
description (:obj:`str`): Product description.
start_parameter (:obj:`str`): Unique bot deep-linking parameter that can be used to
generate this invoice.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer,
**not** float/double). For example, for a price of ``US$ 1.45`` pass ``amount = 145``.
See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
Attributes:
title (:obj:`str`): Product name.
description (:obj:`str`): Product description.
start_parameter (:obj:`str`): Unique bot deep-linking parameter that can be used to
generate this invoice.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer,
**not** float/double). For example, for a price of ``US$ 1.45`` pass ``amount = 145``.
See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
"""
__slots__ = (
"currency",
"description",
"start_parameter",
"title",
"total_amount",
)
def __init__(
self,
title: str,
description: str,
start_parameter: str,
currency: str,
total_amount: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.title: str = title
self.description: str = description
self.start_parameter: str = start_parameter
self.currency: str = currency
self.total_amount: int = total_amount
self._id_attrs = (
self.title,
self.description,
self.start_parameter,
self.currency,
self.total_amount,
)
self._freeze()
MIN_TITLE_LENGTH: Final[int] = constants.InvoiceLimit.MIN_TITLE_LENGTH
""":const:`telegram.constants.InvoiceLimit.MIN_TITLE_LENGTH`
.. versionadded:: 20.0
"""
MAX_TITLE_LENGTH: Final[int] = constants.InvoiceLimit.MAX_TITLE_LENGTH
""":const:`telegram.constants.InvoiceLimit.MAX_TITLE_LENGTH`
.. versionadded:: 20.0
"""
MIN_DESCRIPTION_LENGTH: Final[int] = constants.InvoiceLimit.MIN_DESCRIPTION_LENGTH
""":const:`telegram.constants.InvoiceLimit.MIN_DESCRIPTION_LENGTH`
.. versionadded:: 20.0
"""
MAX_DESCRIPTION_LENGTH: Final[int] = constants.InvoiceLimit.MAX_DESCRIPTION_LENGTH
""":const:`telegram.constants.InvoiceLimit.MAX_DESCRIPTION_LENGTH`
.. versionadded:: 20.0
"""
MIN_PAYLOAD_LENGTH: Final[int] = constants.InvoiceLimit.MIN_PAYLOAD_LENGTH
""":const:`telegram.constants.InvoiceLimit.MIN_PAYLOAD_LENGTH`
.. versionadded:: 20.0
"""
MAX_PAYLOAD_LENGTH: Final[int] = constants.InvoiceLimit.MAX_PAYLOAD_LENGTH
""":const:`telegram.constants.InvoiceLimit.MAX_PAYLOAD_LENGTH`
.. versionadded:: 20.0
"""
MAX_TIP_AMOUNTS: Final[int] = constants.InvoiceLimit.MAX_TIP_AMOUNTS
""":const:`telegram.constants.InvoiceLimit.MAX_TIP_AMOUNTS`
.. versionadded:: 20.0
"""
@@ -1,65 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 LabeledPrice."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class LabeledPrice(TelegramObject):
"""This object represents a portion of the price for goods or services.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`label` and :attr:`amount` are equal.
Examples:
:any:`Payment Bot <examples.paymentbot>`
Args:
label (:obj:`str`): Portion label.
amount (:obj:`int`): Price of the product in the smallest units of the currency (integer,
**not** float/double). For example, for a price of ``US$ 1.45`` pass ``amount = 145``.
See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
Attributes:
label (:obj:`str`): Portion label.
amount (:obj:`int`): Price of the product in the smallest units of the currency (integer,
**not** float/double). For example, for a price of ``US$ 1.45`` pass ``amount = 145``.
See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
"""
__slots__ = ("amount", "label")
def __init__(self, label: str, amount: int, *, api_kwargs: Optional[JSONDict] = None):
super().__init__(api_kwargs=api_kwargs)
self.label: str = label
self.amount: int = amount
self._id_attrs = (self.label, self.amount)
self._freeze()
@@ -1,85 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 OrderInfo."""
from typing import TYPE_CHECKING, Optional
from telegram._payment.shippingaddress import ShippingAddress
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class OrderInfo(TelegramObject):
"""This object represents information about an order.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`name`, :attr:`phone_number`, :attr:`email` and
:attr:`shipping_address` are equal.
Args:
name (:obj:`str`, optional): User name.
phone_number (:obj:`str`, optional): User's phone number.
email (:obj:`str`, optional): User email.
shipping_address (:class:`telegram.ShippingAddress`, optional): User shipping address.
Attributes:
name (:obj:`str`): Optional. User name.
phone_number (:obj:`str`): Optional. User's phone number.
email (:obj:`str`): Optional. User email.
shipping_address (:class:`telegram.ShippingAddress`): Optional. User shipping address.
"""
__slots__ = ("email", "name", "phone_number", "shipping_address")
def __init__(
self,
name: Optional[str] = None,
phone_number: Optional[str] = None,
email: Optional[str] = None,
shipping_address: Optional[ShippingAddress] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.name: Optional[str] = name
self.phone_number: Optional[str] = phone_number
self.email: Optional[str] = email
self.shipping_address: Optional[ShippingAddress] = shipping_address
self._id_attrs = (self.name, self.phone_number, self.email, self.shipping_address)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["OrderInfo"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return cls()
data["shipping_address"] = ShippingAddress.de_json(data.get("shipping_address"), bot)
return super().de_json(data=data, bot=bot)
@@ -1,155 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 PreCheckoutQuery."""
from typing import TYPE_CHECKING, Optional
from telegram._payment.orderinfo import OrderInfo
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
if TYPE_CHECKING:
from telegram import Bot
class PreCheckoutQuery(TelegramObject):
"""This object contains information about an incoming pre-checkout query.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal.
Note:
In Python :keyword:`from` is a reserved word. Use :paramref:`from_user` instead.
Args:
id (:obj:`str`): Unique query identifier.
from_user (:class:`telegram.User`): User who sent the query.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer,
**not** float/double). For example, for a price of ``US$ 1.45`` pass ``amount = 145``.
See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_option_id (:obj:`str`, optional): Identifier of the shipping option chosen by the
user.
order_info (:class:`telegram.OrderInfo`, optional): Order info provided by the user.
Attributes:
id (:obj:`str`): Unique query identifier.
from_user (:class:`telegram.User`): User who sent the query.
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer,
**not** float/double). For example, for a price of ``US$ 1.45`` pass ``amount = 145``.
See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_option_id (:obj:`str`): Optional. Identifier of the shipping option chosen by the
user.
order_info (:class:`telegram.OrderInfo`): Optional. Order info provided by the user.
"""
__slots__ = (
"currency",
"from_user",
"id",
"invoice_payload",
"order_info",
"shipping_option_id",
"total_amount",
)
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
from_user: User,
currency: str,
total_amount: int,
invoice_payload: str,
shipping_option_id: Optional[str] = None,
order_info: Optional[OrderInfo] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.from_user: User = from_user
self.currency: str = currency
self.total_amount: int = total_amount
self.invoice_payload: str = invoice_payload
self.shipping_option_id: Optional[str] = shipping_option_id
self.order_info: Optional[OrderInfo] = order_info
self._id_attrs = (self.id,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PreCheckoutQuery"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["order_info"] = OrderInfo.de_json(data.get("order_info"), bot)
return super().de_json(data=data, bot=bot)
async def answer(
self,
ok: bool,
error_message: Optional[str] = None,
*,
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,
) -> bool:
"""Shortcut for::
await bot.answer_pre_checkout_query(update.pre_checkout_query.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.answer_pre_checkout_query`.
"""
return await self.get_bot().answer_pre_checkout_query(
pre_checkout_query_id=self.id,
ok=ok,
error_message=error_message,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
@@ -1,93 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 RefundedPayment."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class RefundedPayment(TelegramObject):
"""This object contains basic information about a refunded payment.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`telegram_payment_charge_id` is equal.
.. versionadded:: 21.4
Args:
currency (:obj:`str`): Three-letter ISO 4217 `currency
<https://core.telegram.org/bots/payments#supported-currencies>`_ code, or ``XTR`` for
payments in |tg_stars|. Currently, always ``XTR``.
total_amount (:obj:`int`): Total refunded price in the *smallest units* of the currency
(integer, **not** float/double). For example, for a price of ``US$ 1.45``,
``total_amount = 145``. See the *exp* parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot-specified invoice payload.
telegram_payment_charge_id (:obj:`str`): Telegram payment identifier.
provider_payment_charge_id (:obj:`str`, optional): Provider payment identifier.
Attributes:
currency (:obj:`str`): Three-letter ISO 4217 `currency
<https://core.telegram.org/bots/payments#supported-currencies>`_ code, or ``XTR`` for
payments in |tg_stars|. Currently, always ``XTR``.
total_amount (:obj:`int`): Total refunded price in the *smallest units* of the currency
(integer, **not** float/double). For example, for a price of ``US$ 1.45``,
``total_amount = 145``. See the *exp* parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot-specified invoice payload.
telegram_payment_charge_id (:obj:`str`): Telegram payment identifier.
provider_payment_charge_id (:obj:`str`): Optional. Provider payment identifier.
"""
__slots__ = (
"currency",
"invoice_payload",
"provider_payment_charge_id",
"telegram_payment_charge_id",
"total_amount",
)
def __init__(
self,
currency: str,
total_amount: int,
invoice_payload: str,
telegram_payment_charge_id: str,
provider_payment_charge_id: Optional[str] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.currency: str = currency
self.total_amount: int = total_amount
self.invoice_payload: str = invoice_payload
self.telegram_payment_charge_id: str = telegram_payment_charge_id
# Optional
self.provider_payment_charge_id: Optional[str] = provider_payment_charge_id
self._id_attrs = (self.telegram_payment_charge_id,)
self._freeze()
@@ -1,89 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 ShippingAddress."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class ShippingAddress(TelegramObject):
"""This object represents a Telegram ShippingAddress.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`country_code`, :attr:`state`, :attr:`city`,
:attr:`street_line1`, :attr:`street_line2` and :attr:`post_code` are equal.
Args:
country_code (:obj:`str`): ISO 3166-1 alpha-2 country code.
state (:obj:`str`): State, if applicable.
city (:obj:`str`): City.
street_line1 (:obj:`str`): First line for the address.
street_line2 (:obj:`str`): Second line for the address.
post_code (:obj:`str`): Address post code.
Attributes:
country_code (:obj:`str`): ISO 3166-1 alpha-2 country code.
state (:obj:`str`): State, if applicable.
city (:obj:`str`): City.
street_line1 (:obj:`str`): First line for the address.
street_line2 (:obj:`str`): Second line for the address.
post_code (:obj:`str`): Address post code.
"""
__slots__ = (
"city",
"country_code",
"post_code",
"state",
"street_line1",
"street_line2",
)
def __init__(
self,
country_code: str,
state: str,
city: str,
street_line1: str,
street_line2: str,
post_code: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.country_code: str = country_code
self.state: str = state
self.city: str = city
self.street_line1: str = street_line1
self.street_line2: str = street_line2
self.post_code: str = post_code
self._id_attrs = (
self.country_code,
self.state,
self.city,
self.street_line1,
self.street_line2,
self.post_code,
)
self._freeze()
@@ -1,76 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 ShippingOption."""
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
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 LabeledPrice
class ShippingOption(TelegramObject):
"""This object represents one shipping option.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal.
Examples:
:any:`Payment Bot <examples.paymentbot>`
Args:
id (:obj:`str`): Shipping option identifier.
title (:obj:`str`): Option title.
prices (Sequence[:class:`telegram.LabeledPrice`]): List of price portions.
.. versionchanged:: 20.0
|sequenceclassargs|
Attributes:
id (:obj:`str`): Shipping option identifier.
title (:obj:`str`): Option title.
prices (tuple[:class:`telegram.LabeledPrice`]): List of price portions.
.. versionchanged:: 20.0
|tupleclassattrs|
"""
__slots__ = ("id", "prices", "title")
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
title: str,
prices: Sequence["LabeledPrice"],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.title: str = title
self.prices: tuple[LabeledPrice, ...] = parse_sequence_arg(prices)
self._id_attrs = (self.id,)
self._freeze()
@@ -1,125 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 ShippingQuery."""
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._payment.shippingaddress import ShippingAddress
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
if TYPE_CHECKING:
from telegram import Bot
from telegram._payment.shippingoption import ShippingOption
class ShippingQuery(TelegramObject):
"""This object contains information about an incoming shipping query.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal.
Note:
In Python :keyword:`from` is a reserved word. Use :paramref:`from_user` instead.
Args:
id (:obj:`str`): Unique query identifier.
from_user (:class:`telegram.User`): User who sent the query.
invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_address (:class:`telegram.ShippingAddress`): User specified shipping address.
Attributes:
id (:obj:`str`): Unique query identifier.
from_user (:class:`telegram.User`): User who sent the query.
invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_address (:class:`telegram.ShippingAddress`): User specified shipping address.
"""
__slots__ = ("from_user", "id", "invoice_payload", "shipping_address")
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
from_user: User,
invoice_payload: str,
shipping_address: ShippingAddress,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.from_user: User = from_user
self.invoice_payload: str = invoice_payload
self.shipping_address: ShippingAddress = shipping_address
self._id_attrs = (self.id,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ShippingQuery"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["shipping_address"] = ShippingAddress.de_json(data.get("shipping_address"), bot)
return super().de_json(data=data, bot=bot)
async def answer(
self,
ok: bool,
shipping_options: Optional[Sequence["ShippingOption"]] = None,
error_message: Optional[str] = None,
*,
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,
) -> bool:
"""Shortcut for::
await bot.answer_shipping_query(update.shipping_query.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.answer_shipping_query`.
"""
return await self.get_bot().answer_shipping_query(
shipping_query_id=self.id,
ok=ok,
shipping_options=shipping_options,
error_message=error_message,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
@@ -1,120 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 for Telegram Stars affiliates."""
from typing import TYPE_CHECKING, Optional
from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class AffiliateInfo(TelegramObject):
"""Contains information about the affiliate that received a commission via this transaction.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`affiliate_user`, :attr:`affiliate_chat`,
:attr:`commission_per_mille`, :attr:`amount`, and :attr:`nanostar_amount` are equal.
.. versionadded:: 21.9
Args:
affiliate_user (:class:`telegram.User`, optional): The bot or the user that received an
affiliate commission if it was received by a bot or a user
affiliate_chat (:class:`telegram.Chat`, optional): The chat that received an affiliate
commission if it was received by a chat
commission_per_mille (:obj:`int`): The number of Telegram Stars received by the affiliate
for each 1000 Telegram Stars received by the bot from referred users
amount (:obj:`int`): Integer amount of Telegram Stars received by the affiliate from the
transaction, rounded to 0; can be negative for refunds
nanostar_amount (:obj:`int`, optional): The number of
:tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram
Stars received by the affiliate; from
:tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MIN_AMOUNT` to
:tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`;
can be negative for refunds
Attributes:
affiliate_user (:class:`telegram.User`): Optional. The bot or the user that received an
affiliate commission if it was received by a bot or a user
affiliate_chat (:class:`telegram.Chat`): Optional. The chat that received an affiliate
commission if it was received by a chat
commission_per_mille (:obj:`int`): The number of Telegram Stars received by the affiliate
for each 1000 Telegram Stars received by the bot from referred users
amount (:obj:`int`): Integer amount of Telegram Stars received by the affiliate from the
transaction, rounded to 0; can be negative for refunds
nanostar_amount (:obj:`int`): Optional. The number of
:tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram
Stars received by the affiliate; from
:tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MIN_AMOUNT` to
:tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`;
can be negative for refunds
"""
__slots__ = (
"affiliate_chat",
"affiliate_user",
"amount",
"commission_per_mille",
"nanostar_amount",
)
def __init__(
self,
commission_per_mille: int,
amount: int,
affiliate_user: Optional["User"] = None,
affiliate_chat: Optional["Chat"] = None,
nanostar_amount: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.affiliate_user: Optional[User] = affiliate_user
self.affiliate_chat: Optional[Chat] = affiliate_chat
self.commission_per_mille: int = commission_per_mille
self.amount: int = amount
self.nanostar_amount: Optional[int] = nanostar_amount
self._id_attrs = (
self.affiliate_user,
self.affiliate_chat,
self.commission_per_mille,
self.amount,
self.nanostar_amount,
)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["AffiliateInfo"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["affiliate_user"] = User.de_json(data.get("affiliate_user"), bot)
data["affiliate_chat"] = Chat.de_json(data.get("affiliate_chat"), bot)
return super().de_json(data=data, bot=bot)
@@ -1,188 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 for Telegram Stars Revenue Withdrawals."""
import datetime as dtm
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class RevenueWithdrawalState(TelegramObject):
"""This object describes the state of a revenue withdrawal operation. Currently, it can be one
of:
* :class:`telegram.RevenueWithdrawalStatePending`
* :class:`telegram.RevenueWithdrawalStateSucceeded`
* :class:`telegram.RevenueWithdrawalStateFailed`
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: 21.4
Args:
type (:obj:`str`): The type of the state.
Attributes:
type (:obj:`str`): The type of the state.
"""
__slots__ = ("type",)
PENDING: Final[str] = constants.RevenueWithdrawalStateType.PENDING
""":const:`telegram.constants.RevenueWithdrawalStateType.PENDING`"""
SUCCEEDED: Final[str] = constants.RevenueWithdrawalStateType.SUCCEEDED
""":const:`telegram.constants.RevenueWithdrawalStateType.SUCCEEDED`"""
FAILED: Final[str] = constants.RevenueWithdrawalStateType.FAILED
""":const:`telegram.constants.RevenueWithdrawalStateType.FAILED`"""
def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(api_kwargs=api_kwargs)
self.type: str = enum.get_member(constants.RevenueWithdrawalStateType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["RevenueWithdrawalState"]:
"""Converts JSON data to the appropriate :class:`RevenueWithdrawalState` object, i.e. takes
care of selecting the correct subclass.
Args:
data (dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
data = cls._parse_data(data)
if (cls is RevenueWithdrawalState and not data) or data is None:
return None
_class_mapping: dict[str, type[RevenueWithdrawalState]] = {
cls.PENDING: RevenueWithdrawalStatePending,
cls.SUCCEEDED: RevenueWithdrawalStateSucceeded,
cls.FAILED: RevenueWithdrawalStateFailed,
}
if cls is RevenueWithdrawalState and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
return super().de_json(data=data, bot=bot)
class RevenueWithdrawalStatePending(RevenueWithdrawalState):
"""The withdrawal is in progress.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the state, always
:tg-const:`telegram.RevenueWithdrawalState.PENDING`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=RevenueWithdrawalState.PENDING, api_kwargs=api_kwargs)
self._freeze()
class RevenueWithdrawalStateSucceeded(RevenueWithdrawalState):
"""The withdrawal succeeded.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`date` are equal.
.. versionadded:: 21.4
Args:
date (:obj:`datetime.datetime`): Date the withdrawal was completed as a datetime object.
url (:obj:`str`): An HTTPS URL that can be used to see transaction details.
Attributes:
type (:obj:`str`): The type of the state, always
:tg-const:`telegram.RevenueWithdrawalState.SUCCEEDED`.
date (:obj:`datetime.datetime`): Date the withdrawal was completed as a datetime object.
url (:obj:`str`): An HTTPS URL that can be used to see transaction details.
"""
__slots__ = ("date", "url")
def __init__(
self,
date: dtm.datetime,
url: str,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=RevenueWithdrawalState.SUCCEEDED, api_kwargs=api_kwargs)
with self._unfrozen():
self.date: dtm.datetime = date
self.url: str = url
self._id_attrs = (
self.type,
self.date,
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["RevenueWithdrawalStateSucceeded"]:
"""See :meth:`telegram.RevenueWithdrawalState.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class RevenueWithdrawalStateFailed(RevenueWithdrawalState):
"""The withdrawal failed and the transaction was refunded.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the state, always
:tg-const:`telegram.RevenueWithdrawalState.FAILED`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=RevenueWithdrawalState.FAILED, api_kwargs=api_kwargs)
self._freeze()
@@ -1,172 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 for Telegram Stars transactions."""
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
from .transactionpartner import TransactionPartner
if TYPE_CHECKING:
from telegram import Bot
class StarTransaction(TelegramObject):
"""Describes a Telegram Star transaction.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id`, :attr:`source`, and :attr:`receiver` are equal.
.. versionadded:: 21.4
Args:
id (:obj:`str`): Unique identifier of the transaction. Coincides with the identifer
of the original transaction for refund transactions.
Coincides with :attr:`SuccessfulPayment.telegram_payment_charge_id` for
successful incoming payments from users.
amount (:obj:`int`): Integer amount of Telegram Stars transferred by the transaction.
nanostar_amount (:obj:`int`, optional): The number of
:tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram
Stars transferred by the transaction; from 0 to
:tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`
.. versionadded:: 21.9
date (:obj:`datetime.datetime`): Date the transaction was created as a datetime object.
source (:class:`telegram.TransactionPartner`, optional): Source of an incoming transaction
(e.g., a user purchasing goods or services, Fragment refunding a failed withdrawal).
Only for incoming transactions.
receiver (:class:`telegram.TransactionPartner`, optional): Receiver of an outgoing
transaction (e.g., a user for a purchase refund, Fragment for a withdrawal). Only for
outgoing transactions.
Attributes:
id (:obj:`str`): Unique identifier of the transaction. Coincides with the identifer
of the original transaction for refund transactions.
Coincides with :attr:`SuccessfulPayment.telegram_payment_charge_id` for
successful incoming payments from users.
amount (:obj:`int`): Integer amount of Telegram Stars transferred by the transaction.
nanostar_amount (:obj:`int`): Optional. The number of
:tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram
Stars transferred by the transaction; from 0 to
:tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`
.. versionadded:: 21.9
date (:obj:`datetime.datetime`): Date the transaction was created as a datetime object.
source (:class:`telegram.TransactionPartner`): Optional. Source of an incoming transaction
(e.g., a user purchasing goods or services, Fragment refunding a failed withdrawal).
Only for incoming transactions.
receiver (:class:`telegram.TransactionPartner`): Optional. Receiver of an outgoing
transaction (e.g., a user for a purchase refund, Fragment for a withdrawal). Only for
outgoing transactions.
"""
__slots__ = ("amount", "date", "id", "nanostar_amount", "receiver", "source")
def __init__(
self,
id: str,
amount: int,
date: dtm.datetime,
source: Optional[TransactionPartner] = None,
receiver: Optional[TransactionPartner] = None,
nanostar_amount: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.amount: int = amount
self.date: dtm.datetime = date
self.source: Optional[TransactionPartner] = source
self.receiver: Optional[TransactionPartner] = receiver
self.nanostar_amount: Optional[int] = nanostar_amount
self._id_attrs = (
self.id,
self.source,
self.receiver,
)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["StarTransaction"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
data["source"] = TransactionPartner.de_json(data.get("source"), bot)
data["receiver"] = TransactionPartner.de_json(data.get("receiver"), bot)
return super().de_json(data=data, bot=bot)
class StarTransactions(TelegramObject):
"""
Contains a list of Telegram Star transactions.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`transactions` are equal.
.. versionadded:: 21.4
Args:
transactions (Sequence[:class:`telegram.StarTransaction`]): The list of transactions.
Attributes:
transactions (tuple[:class:`telegram.StarTransaction`]): The list of transactions.
"""
__slots__ = ("transactions",)
def __init__(
self, transactions: Sequence[StarTransaction], *, api_kwargs: Optional[JSONDict] = None
):
super().__init__(api_kwargs=api_kwargs)
self.transactions: tuple[StarTransaction, ...] = parse_sequence_arg(transactions)
self._id_attrs = (self.transactions,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["StarTransactions"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["transactions"] = StarTransaction.de_list(data.get("transactions"), bot)
return super().de_json(data=data, bot=bot)
@@ -1,405 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 for Telegram Stars transaction partners."""
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._gifts import Gift
from telegram._paidmedia import PaidMedia
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from .affiliateinfo import AffiliateInfo
from .revenuewithdrawalstate import RevenueWithdrawalState
if TYPE_CHECKING:
from telegram import Bot
class TransactionPartner(TelegramObject):
"""This object describes the source of a transaction, or its recipient for outgoing
transactions. Currently, it can be one of:
* :class:`TransactionPartnerUser`
* :class:`TransactionPartnerAffiliateProgram`
* :class:`TransactionPartnerFragment`
* :class:`TransactionPartnerTelegramAds`
* :class:`TransactionPartnerTelegramApi`
* :class:`TransactionPartnerOther`
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: 21.4
Args:
type (:obj:`str`): The type of the transaction partner.
Attributes:
type (:obj:`str`): The type of the transaction partner.
"""
__slots__ = ("type",)
AFFILIATE_PROGRAM: Final[str] = constants.TransactionPartnerType.AFFILIATE_PROGRAM
""":const:`telegram.constants.TransactionPartnerType.AFFILIATE_PROGRAM`
.. versionadded:: 21.9
"""
FRAGMENT: Final[str] = constants.TransactionPartnerType.FRAGMENT
""":const:`telegram.constants.TransactionPartnerType.FRAGMENT`"""
OTHER: Final[str] = constants.TransactionPartnerType.OTHER
""":const:`telegram.constants.TransactionPartnerType.OTHER`"""
TELEGRAM_ADS: Final[str] = constants.TransactionPartnerType.TELEGRAM_ADS
""":const:`telegram.constants.TransactionPartnerType.TELEGRAM_ADS`"""
TELEGRAM_API: Final[str] = constants.TransactionPartnerType.TELEGRAM_API
""":const:`telegram.constants.TransactionPartnerType.TELEGRAM_API`"""
USER: Final[str] = constants.TransactionPartnerType.USER
""":const:`telegram.constants.TransactionPartnerType.USER`"""
def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(api_kwargs=api_kwargs)
self.type: str = enum.get_member(constants.TransactionPartnerType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartner"]:
"""Converts JSON data to the appropriate :class:`TransactionPartner` object, i.e. takes
care of selecting the correct subclass.
Args:
data (dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
data = cls._parse_data(data)
if (cls is TransactionPartner and not data) or data is None:
return None
_class_mapping: dict[str, type[TransactionPartner]] = {
cls.AFFILIATE_PROGRAM: TransactionPartnerAffiliateProgram,
cls.FRAGMENT: TransactionPartnerFragment,
cls.USER: TransactionPartnerUser,
cls.TELEGRAM_ADS: TransactionPartnerTelegramAds,
cls.TELEGRAM_API: TransactionPartnerTelegramApi,
cls.OTHER: TransactionPartnerOther,
}
if cls is TransactionPartner and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
return super().de_json(data=data, bot=bot)
class TransactionPartnerAffiliateProgram(TransactionPartner):
"""Describes the affiliate program that issued the affiliate commission received via this
transaction.
This object is comparable in terms of equality. Two objects of this class are considered equal,
if their :attr:`commission_per_mille` are equal.
.. versionadded:: 21.9
Args:
sponsor_user (:class:`telegram.User`, optional): Information about the bot that sponsored
the affiliate program
commission_per_mille (:obj:`int`): The number of Telegram Stars received by the bot for
each 1000 Telegram Stars received by the affiliate program sponsor from referred users.
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.AFFILIATE_PROGRAM`.
sponsor_user (:class:`telegram.User`): Optional. Information about the bot that sponsored
the affiliate program
commission_per_mille (:obj:`int`): The number of Telegram Stars received by the bot for
each 1000 Telegram Stars received by the affiliate program sponsor from referred users.
"""
__slots__ = ("commission_per_mille", "sponsor_user")
def __init__(
self,
commission_per_mille: int,
sponsor_user: Optional["User"] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=TransactionPartner.AFFILIATE_PROGRAM, api_kwargs=api_kwargs)
with self._unfrozen():
self.sponsor_user: Optional[User] = sponsor_user
self.commission_per_mille: int = commission_per_mille
self._id_attrs = (
self.type,
self.commission_per_mille,
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerAffiliateProgram"]:
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["sponsor_user"] = User.de_json(data.get("sponsor_user"), bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class TransactionPartnerFragment(TransactionPartner):
"""Describes a withdrawal transaction with Fragment.
.. versionadded:: 21.4
Args:
withdrawal_state (:class:`telegram.RevenueWithdrawalState`, optional): State of the
transaction if the transaction is outgoing.
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.FRAGMENT`.
withdrawal_state (:class:`telegram.RevenueWithdrawalState`): Optional. State of the
transaction if the transaction is outgoing.
"""
__slots__ = ("withdrawal_state",)
def __init__(
self,
withdrawal_state: Optional["RevenueWithdrawalState"] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=TransactionPartner.FRAGMENT, api_kwargs=api_kwargs)
with self._unfrozen():
self.withdrawal_state: Optional[RevenueWithdrawalState] = withdrawal_state
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerFragment"]:
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["withdrawal_state"] = RevenueWithdrawalState.de_json(
data.get("withdrawal_state"), bot
)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class TransactionPartnerUser(TransactionPartner):
"""Describes a transaction with a user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`user` are equal.
.. versionadded:: 21.4
Args:
user (:class:`telegram.User`): Information about the user.
affiliate (:class:`telegram.AffiliateInfo`, optional): Information about the affiliate that
received a commission via this transaction
.. versionadded:: 21.9
invoice_payload (:obj:`str`, optional): Bot-specified invoice payload.
subscription_period (:class:`datetime.timedelta`, optional): The duration of the paid
subscription
.. versionadded:: 21.8
paid_media (Sequence[:class:`telegram.PaidMedia`], optional): Information about the paid
media bought by the user.
.. versionadded:: 21.5
paid_media_payload (:obj:`str`, optional): Bot-specified paid media payload.
.. versionadded:: 21.6
gift (:class:`telegram.Gift`, optional): The gift sent to the user by the bot
.. versionadded:: 21.8
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.USER`.
user (:class:`telegram.User`): Information about the user.
affiliate (:class:`telegram.AffiliateInfo`): Optional. Information about the affiliate that
received a commission via this transaction
.. versionadded:: 21.9
invoice_payload (:obj:`str`): Optional. Bot-specified invoice payload.
subscription_period (:class:`datetime.timedelta`): Optional. The duration of the paid
subscription
.. versionadded:: 21.8
paid_media (tuple[:class:`telegram.PaidMedia`]): Optional. Information about the paid
media bought by the user.
.. versionadded:: 21.5
paid_media_payload (:obj:`str`): Optional. Bot-specified paid media payload.
.. versionadded:: 21.6
gift (:class:`telegram.Gift`): Optional. The gift sent to the user by the bot
.. versionadded:: 21.8
"""
__slots__ = (
"affiliate",
"gift",
"invoice_payload",
"paid_media",
"paid_media_payload",
"subscription_period",
"user",
)
def __init__(
self,
user: "User",
invoice_payload: Optional[str] = None,
paid_media: Optional[Sequence[PaidMedia]] = None,
paid_media_payload: Optional[str] = None,
subscription_period: Optional[dtm.timedelta] = None,
gift: Optional[Gift] = None,
affiliate: Optional[AffiliateInfo] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=TransactionPartner.USER, api_kwargs=api_kwargs)
with self._unfrozen():
self.user: User = user
self.affiliate: Optional[AffiliateInfo] = affiliate
self.invoice_payload: Optional[str] = invoice_payload
self.paid_media: Optional[tuple[PaidMedia, ...]] = parse_sequence_arg(paid_media)
self.paid_media_payload: Optional[str] = paid_media_payload
self.subscription_period: Optional[dtm.timedelta] = subscription_period
self.gift: Optional[Gift] = gift
self._id_attrs = (
self.type,
self.user,
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerUser"]:
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["user"] = User.de_json(data.get("user"), bot)
data["affiliate"] = AffiliateInfo.de_json(data.get("affiliate"), bot)
data["paid_media"] = PaidMedia.de_list(data.get("paid_media"), bot=bot)
data["subscription_period"] = (
dtm.timedelta(seconds=sp)
if (sp := data.get("subscription_period")) is not None
else None
)
data["gift"] = Gift.de_json(data.get("gift"), bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class TransactionPartnerOther(TransactionPartner):
"""Describes a transaction with an unknown partner.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.OTHER`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.OTHER, api_kwargs=api_kwargs)
self._freeze()
class TransactionPartnerTelegramAds(TransactionPartner):
"""Describes a withdrawal transaction to the Telegram Ads platform.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.TELEGRAM_ADS`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.TELEGRAM_ADS, api_kwargs=api_kwargs)
self._freeze()
class TransactionPartnerTelegramApi(TransactionPartner):
"""Describes a transaction with payment for
`paid broadcasting <https://core.telegram.org/bots/api#paid-broadcasts>`_.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_count` is equal.
.. versionadded:: 21.7
Args:
request_count (:obj:`int`): The number of successful requests that exceeded regular limits
and were therefore billed.
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.TELEGRAM_API`.
request_count (:obj:`int`): The number of successful requests that exceeded regular limits
and were therefore billed.
"""
__slots__ = ("request_count",)
def __init__(self, request_count: int, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.TELEGRAM_API, api_kwargs=api_kwargs)
with self._unfrozen():
self.request_count: int = request_count
self._id_attrs = (self.request_count,)
@@ -1,159 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 SuccessfulPayment."""
import datetime as dtm
from typing import TYPE_CHECKING, Optional
from telegram._payment.orderinfo import OrderInfo
from telegram._telegramobject import TelegramObject
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class SuccessfulPayment(TelegramObject):
"""This object contains basic information about a successful payment.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`telegram_payment_charge_id` and
:attr:`provider_payment_charge_id` are equal.
Args:
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer,
**not** float/double). For example, for a price of ``US$ 1.45`` pass ``amount = 145``.
See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot-specified invoice payload.
subscription_expiration_date (:class:`datetime.datetime`, optional): Expiration date of the
subscription; for recurring payments only.
.. versionadded:: 21.8
is_recurring (:obj:`bool`, optional): True, if the payment is for a subscription.
.. versionadded:: 21.8
is_first_recurring (:obj:`bool`, optional): True, if the payment is the first payment of a
subscription.
.. versionadded:: 21.8
shipping_option_id (:obj:`str`, optional): Identifier of the shipping option chosen by the
user.
order_info (:class:`telegram.OrderInfo`, optional): Order info provided by the user.
telegram_payment_charge_id (:obj:`str`): Telegram payment identifier.
provider_payment_charge_id (:obj:`str`): Provider payment identifier.
Attributes:
currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in
|tg_stars|.
total_amount (:obj:`int`): Total price in the smallest units of the currency (integer,
**not** float/double). For example, for a price of ``US$ 1.45`` pass ``amount = 145``.
See the ``exp`` parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot-specified invoice payload.
subscription_expiration_date (:class:`datetime.datetime`): Optional. Expiration
date of the subscription; for recurring payments only.
.. versionadded:: 21.8
is_recurring (:obj:`bool`): Optional. True, if the payment is for a subscription.
.. versionadded:: 21.8
is_first_recurring (:obj:`bool`): Optional. True, if the payment is the first payment of a
subscription.
.. versionadded:: 21.8
shipping_option_id (:obj:`str`): Optional. Identifier of the shipping option chosen by the
user.
order_info (:class:`telegram.OrderInfo`): Optional. Order info provided by the user.
telegram_payment_charge_id (:obj:`str`): Telegram payment identifier.
provider_payment_charge_id (:obj:`str`): Provider payment identifier.
"""
__slots__ = (
"currency",
"invoice_payload",
"is_first_recurring",
"is_recurring",
"order_info",
"provider_payment_charge_id",
"shipping_option_id",
"subscription_expiration_date",
"telegram_payment_charge_id",
"total_amount",
)
def __init__(
self,
currency: str,
total_amount: int,
invoice_payload: str,
telegram_payment_charge_id: str,
provider_payment_charge_id: str,
shipping_option_id: Optional[str] = None,
order_info: Optional[OrderInfo] = None,
subscription_expiration_date: Optional[dtm.datetime] = None,
is_recurring: Optional[bool] = None,
is_first_recurring: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.currency: str = currency
self.total_amount: int = total_amount
self.invoice_payload: str = invoice_payload
self.shipping_option_id: Optional[str] = shipping_option_id
self.order_info: Optional[OrderInfo] = order_info
self.telegram_payment_charge_id: str = telegram_payment_charge_id
self.provider_payment_charge_id: str = provider_payment_charge_id
self.subscription_expiration_date: Optional[dtm.datetime] = subscription_expiration_date
self.is_recurring: Optional[bool] = is_recurring
self.is_first_recurring: Optional[bool] = is_first_recurring
self._id_attrs = (self.telegram_payment_charge_id, self.provider_payment_charge_id)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["SuccessfulPayment"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["order_info"] = OrderInfo.de_json(data.get("order_info"), bot)
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["subscription_expiration_date"] = from_timestamp(
data.get("subscription_expiration_date"), tzinfo=loc_tzinfo
)
return super().de_json(data=data, bot=bot)
@@ -1,650 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 Poll."""
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._chat import Chat
from telegram._messageentity import MessageEntity
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.entities import parse_message_entities, parse_message_entity
from telegram._utils.types import JSONDict, ODVInput
if TYPE_CHECKING:
from telegram import Bot
class InputPollOption(TelegramObject):
"""
This object contains information about one answer option in a poll to be sent.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text` is equal.
.. versionadded:: 21.2
Args:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
text_parse_mode (:obj:`str`, optional): |parse_mode|
Currently, only custom emoji entities are allowed.
text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities
that appear in the option :paramref:`text`. It can be specified instead of
:paramref:`text_parse_mode`.
Currently, only custom emoji entities are allowed.
This list is empty if the text does not contain entities.
Attributes:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
text_parse_mode (:obj:`str`): Optional. |parse_mode|
Currently, only custom emoji entities are allowed.
text_entities (Sequence[:class:`telegram.MessageEntity`]): Special entities
that appear in the option :paramref:`text`. It can be specified instead of
:paramref:`text_parse_mode`.
Currently, only custom emoji entities are allowed.
This list is empty if the text does not contain entities.
"""
__slots__ = ("text", "text_entities", "text_parse_mode")
def __init__(
self,
text: str,
text_parse_mode: ODVInput[str] = DEFAULT_NONE,
text_entities: Optional[Sequence[MessageEntity]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.text: str = text
self.text_parse_mode: ODVInput[str] = text_parse_mode
self.text_entities: tuple[MessageEntity, ...] = parse_sequence_arg(text_entities)
self._id_attrs = (self.text,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["InputPollOption"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
return super().de_json(data=data, bot=bot)
class PollOption(TelegramObject):
"""
This object contains information about one answer option in a poll.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text` and :attr:`voter_count` are equal.
Args:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
voter_count (:obj:`int`): Number of users that voted for this option.
text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities
that appear in the option text. Currently, only custom emoji entities are allowed in
poll option texts.
.. versionadded:: 21.2
Attributes:
text (:obj:`str`): Option text,
:tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
characters.
voter_count (:obj:`int`): Number of users that voted for this option.
text_entities (tuple[:class:`telegram.MessageEntity`]): Special entities
that appear in the option text. Currently, only custom emoji entities are allowed in
poll option texts.
This list is empty if the question does not contain entities.
.. versionadded:: 21.2
"""
__slots__ = ("text", "text_entities", "voter_count")
def __init__(
self,
text: str,
voter_count: int,
text_entities: Optional[Sequence[MessageEntity]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.text: str = text
self.voter_count: int = voter_count
self.text_entities: tuple[MessageEntity, ...] = parse_sequence_arg(text_entities)
self._id_attrs = (self.text, self.voter_count)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PollOption"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
return super().de_json(data=data, bot=bot)
def parse_entity(self, entity: MessageEntity) -> str:
"""Returns the text in :attr:`text`
from a given :class:`telegram.MessageEntity` of :attr:`text_entities`.
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``Message.text`` with the offset and length.)
.. versionadded:: 21.2
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to :attr:`text_entities`.
Returns:
:obj:`str`: The text of the given entity.
"""
return parse_message_entity(self.text, entity)
def parse_entities(self, types: Optional[list[str]] = None) -> dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this polls question filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
Note:
This method should always be used instead of the :attr:`text_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_entity` for more info.
.. versionadded:: 21.2
Args:
types (list[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
return parse_message_entities(self.text, self.text_entities, types)
MIN_LENGTH: Final[int] = constants.PollLimit.MIN_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MIN_OPTION_LENGTH`
.. versionadded:: 20.0
"""
MAX_LENGTH: Final[int] = constants.PollLimit.MAX_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MAX_OPTION_LENGTH`
.. versionadded:: 20.0
"""
class PollAnswer(TelegramObject):
"""
This object represents an answer of a user in a non-anonymous poll.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`poll_id`, :attr:`user` and :attr:`option_ids` are equal.
.. versionchanged:: 20.5
The order of :paramref:`option_ids` and :paramref:`user` is changed in
20.5 as the latter one became optional.
.. versionchanged:: 20.6
Backward compatiblity for changed order of :paramref:`option_ids` and :paramref:`user`
was removed.
Args:
poll_id (:obj:`str`): Unique poll identifier.
option_ids (Sequence[:obj:`int`]): Identifiers of answer options, chosen by the user. May
be empty if the user retracted their vote.
.. versionchanged:: 20.0
|sequenceclassargs|
user (:class:`telegram.User`, optional): The user that changed the answer to the poll,
if the voter isn't anonymous. If the voter is anonymous, this field will contain the
user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility.
.. versionchanged:: 20.5
:paramref:`user` became optional.
voter_chat (:class:`telegram.Chat`, optional): The chat that changed the answer to the
poll, if the voter is anonymous.
.. versionadded:: 20.5
Attributes:
poll_id (:obj:`str`): Unique poll identifier.
option_ids (tuple[:obj:`int`]): Identifiers of answer options, chosen by the user. May
be empty if the user retracted their vote.
.. versionchanged:: 20.0
|tupleclassattrs|
user (:class:`telegram.User`): Optional. The user, who changed the answer to the
poll, if the voter isn't anonymous. If the voter is anonymous, this field will contain
the user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility
.. versionchanged:: 20.5
:paramref:`user` became optional.
voter_chat (:class:`telegram.Chat`): Optional. The chat that changed the answer to the
poll, if the voter is anonymous.
.. versionadded:: 20.5
"""
__slots__ = ("option_ids", "poll_id", "user", "voter_chat")
def __init__(
self,
poll_id: str,
option_ids: Sequence[int],
user: Optional[User] = None,
voter_chat: Optional[Chat] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.poll_id: str = poll_id
self.voter_chat: Optional[Chat] = voter_chat
self.option_ids: tuple[int, ...] = parse_sequence_arg(option_ids)
self.user: Optional[User] = user
self._id_attrs = (
self.poll_id,
self.option_ids,
self.user,
self.voter_chat,
)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PollAnswer"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["user"] = User.de_json(data.get("user"), bot)
data["voter_chat"] = Chat.de_json(data.get("voter_chat"), bot)
return super().de_json(data=data, bot=bot)
class Poll(TelegramObject):
"""
This object contains information about a poll.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal.
Examples:
:any:`Poll Bot <examples.pollbot>`
Args:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
:tg-const:`telegram.Poll.MAX_QUESTION_LENGTH` characters.
options (Sequence[:class:`~telegram.PollOption`]): List of poll options.
.. versionchanged:: 20.0
|sequenceclassargs|
is_closed (:obj:`bool`): :obj:`True`, if the poll is closed.
is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous.
type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`.
allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers.
correct_option_id (:obj:`int`, optional): A zero based identifier of the correct answer
option. Available only for closed polls in the quiz mode, which were sent
(not forwarded), by the bot or to a private chat with the bot.
explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect
answer or taps on the lamp icon in a quiz-style poll,
0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters.
explanation_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special
entities like usernames, URLs, bot commands, etc. that appear in the
:attr:`explanation`. This list is empty if the message does not contain explanation
entities.
.. versionchanged:: 20.0
* This attribute is now always a (possibly empty) list and never :obj:`None`.
* |sequenceclassargs|
open_period (:obj:`int`, optional): Amount of time in seconds the poll will be active
after creation.
close_date (:obj:`datetime.datetime`, optional): Point in time (Unix timestamp) when the
poll will be automatically closed. Converted to :obj:`datetime.datetime`.
.. versionchanged:: 20.3
|datetime_localization|
question_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities
that appear in the :attr:`question`. Currently, only custom emoji entities are allowed
in poll questions.
.. versionadded:: 21.2
Attributes:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
:tg-const:`telegram.Poll.MAX_QUESTION_LENGTH` characters.
options (tuple[:class:`~telegram.PollOption`]): List of poll options.
.. versionchanged:: 20.0
|tupleclassattrs|
total_voter_count (:obj:`int`): Total number of users that voted in the poll.
is_closed (:obj:`bool`): :obj:`True`, if the poll is closed.
is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous.
type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`.
allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers.
correct_option_id (:obj:`int`): Optional. A zero based identifier of the correct answer
option. Available only for closed polls in the quiz mode, which were sent
(not forwarded), by the bot or to a private chat with the bot.
explanation (:obj:`str`): Optional. Text that is shown when a user chooses an incorrect
answer or taps on the lamp icon in a quiz-style poll,
0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters.
explanation_entities (tuple[:class:`telegram.MessageEntity`]): Special entities
like usernames, URLs, bot commands, etc. that appear in the :attr:`explanation`.
This list is empty if the message does not contain explanation entities.
.. versionchanged:: 20.0
|tupleclassattrs|
.. versionchanged:: 20.0
This attribute is now always a (possibly empty) list and never :obj:`None`.
open_period (:obj:`int`): Optional. Amount of time in seconds the poll will be active
after creation.
close_date (:obj:`datetime.datetime`): Optional. Point in time when the poll will be
automatically closed.
.. versionchanged:: 20.3
|datetime_localization|
question_entities (tuple[:class:`telegram.MessageEntity`]): Special entities
that appear in the :attr:`question`. Currently, only custom emoji entities are allowed
in poll questions.
This list is empty if the question does not contain entities.
.. versionadded:: 21.2
"""
__slots__ = (
"allows_multiple_answers",
"close_date",
"correct_option_id",
"explanation",
"explanation_entities",
"id",
"is_anonymous",
"is_closed",
"open_period",
"options",
"question",
"question_entities",
"total_voter_count",
"type",
)
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
question: str,
options: Sequence[PollOption],
total_voter_count: int,
is_closed: bool,
is_anonymous: bool,
type: str, # pylint: disable=redefined-builtin
allows_multiple_answers: bool,
correct_option_id: Optional[int] = None,
explanation: Optional[str] = None,
explanation_entities: Optional[Sequence[MessageEntity]] = None,
open_period: Optional[int] = None,
close_date: Optional[dtm.datetime] = None,
question_entities: Optional[Sequence[MessageEntity]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.question: str = question
self.options: tuple[PollOption, ...] = parse_sequence_arg(options)
self.total_voter_count: int = total_voter_count
self.is_closed: bool = is_closed
self.is_anonymous: bool = is_anonymous
self.type: str = enum.get_member(constants.PollType, type, type)
self.allows_multiple_answers: bool = allows_multiple_answers
self.correct_option_id: Optional[int] = correct_option_id
self.explanation: Optional[str] = explanation
self.explanation_entities: tuple[MessageEntity, ...] = parse_sequence_arg(
explanation_entities
)
self.open_period: Optional[int] = open_period
self.close_date: Optional[dtm.datetime] = close_date
self.question_entities: tuple[MessageEntity, ...] = parse_sequence_arg(question_entities)
self._id_attrs = (self.id,)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Poll"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["options"] = [PollOption.de_json(option, bot) for option in data["options"]]
data["explanation_entities"] = MessageEntity.de_list(data.get("explanation_entities"), bot)
data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo)
data["question_entities"] = MessageEntity.de_list(data.get("question_entities"), bot)
return super().de_json(data=data, bot=bot)
def parse_explanation_entity(self, entity: MessageEntity) -> str:
"""Returns the text in :attr:`explanation` from a given :class:`telegram.MessageEntity` of
:attr:`explanation_entities`.
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``Message.text`` with the offset and length.)
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to :attr:`explanation_entities`.
Returns:
:obj:`str`: The text of the given entity.
Raises:
RuntimeError: If the poll has no explanation.
"""
if not self.explanation:
raise RuntimeError("This Poll has no 'explanation'.")
return parse_message_entity(self.explanation, entity)
def parse_explanation_entities(
self, types: Optional[list[str]] = None
) -> dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this polls explanation filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
Note:
This method should always be used instead of the :attr:`explanation_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_explanation_entity` for more info.
Args:
types (list[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
Raises:
RuntimeError: If the poll has no explanation.
"""
if not self.explanation:
raise RuntimeError("This Poll has no 'explanation'.")
return parse_message_entities(self.explanation, self.explanation_entities, types)
def parse_question_entity(self, entity: MessageEntity) -> str:
"""Returns the text in :attr:`question` from a given :class:`telegram.MessageEntity` of
:attr:`question_entities`.
.. versionadded:: 21.2
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``Message.text`` with the offset and length.)
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to :attr:`question_entities`.
Returns:
:obj:`str`: The text of the given entity.
"""
return parse_message_entity(self.question, entity)
def parse_question_entities(
self, types: Optional[list[str]] = None
) -> dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this polls question filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
.. versionadded:: 21.2
Note:
This method should always be used instead of the :attr:`question_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_question_entity` for more info.
Args:
types (list[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
return parse_message_entities(self.question, self.question_entities, types)
REGULAR: Final[str] = constants.PollType.REGULAR
""":const:`telegram.constants.PollType.REGULAR`"""
QUIZ: Final[str] = constants.PollType.QUIZ
""":const:`telegram.constants.PollType.QUIZ`"""
MAX_EXPLANATION_LENGTH: Final[int] = constants.PollLimit.MAX_EXPLANATION_LENGTH
""":const:`telegram.constants.PollLimit.MAX_EXPLANATION_LENGTH`
.. versionadded:: 20.0
"""
MAX_EXPLANATION_LINE_FEEDS: Final[int] = constants.PollLimit.MAX_EXPLANATION_LINE_FEEDS
""":const:`telegram.constants.PollLimit.MAX_EXPLANATION_LINE_FEEDS`
.. versionadded:: 20.0
"""
MIN_OPEN_PERIOD: Final[int] = constants.PollLimit.MIN_OPEN_PERIOD
""":const:`telegram.constants.PollLimit.MIN_OPEN_PERIOD`
.. versionadded:: 20.0
"""
MAX_OPEN_PERIOD: Final[int] = constants.PollLimit.MAX_OPEN_PERIOD
""":const:`telegram.constants.PollLimit.MAX_OPEN_PERIOD`
.. versionadded:: 20.0
"""
MIN_QUESTION_LENGTH: Final[int] = constants.PollLimit.MIN_QUESTION_LENGTH
""":const:`telegram.constants.PollLimit.MIN_QUESTION_LENGTH`
.. versionadded:: 20.0
"""
MAX_QUESTION_LENGTH: Final[int] = constants.PollLimit.MAX_QUESTION_LENGTH
""":const:`telegram.constants.PollLimit.MAX_QUESTION_LENGTH`
.. versionadded:: 20.0
"""
MIN_OPTION_LENGTH: Final[int] = constants.PollLimit.MIN_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MIN_OPTION_LENGTH`
.. versionadded:: 20.0
"""
MAX_OPTION_LENGTH: Final[int] = constants.PollLimit.MAX_OPTION_LENGTH
""":const:`telegram.constants.PollLimit.MAX_OPTION_LENGTH`
.. versionadded:: 20.0
"""
MIN_OPTION_NUMBER: Final[int] = constants.PollLimit.MIN_OPTION_NUMBER
""":const:`telegram.constants.PollLimit.MIN_OPTION_NUMBER`
.. versionadded:: 20.0
"""
MAX_OPTION_NUMBER: Final[int] = constants.PollLimit.MAX_OPTION_NUMBER
""":const:`telegram.constants.PollLimit.MAX_OPTION_NUMBER`
.. versionadded:: 20.0
"""
@@ -1,82 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 Proximity Alert."""
from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class ProximityAlertTriggered(TelegramObject):
"""
This object represents the content of a service message, sent whenever a user in the chat
triggers a proximity alert set by another user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`traveler`, :attr:`watcher` and :attr:`distance` are equal.
Args:
traveler (:class:`telegram.User`): User that triggered the alert
watcher (:class:`telegram.User`): User that set the alert
distance (:obj:`int`): The distance between the users
Attributes:
traveler (:class:`telegram.User`): User that triggered the alert
watcher (:class:`telegram.User`): User that set the alert
distance (:obj:`int`): The distance between the users
"""
__slots__ = ("distance", "traveler", "watcher")
def __init__(
self,
traveler: User,
watcher: User,
distance: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.traveler: User = traveler
self.watcher: User = watcher
self.distance: int = distance
self._id_attrs = (self.traveler, self.watcher, self.distance)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ProximityAlertTriggered"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["traveler"] = User.de_json(data.get("traveler"), bot)
data["watcher"] = User.de_json(data.get("watcher"), bot)
return super().de_json(data=data, bot=bot)
@@ -1,244 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 objects that represents a Telegram ReactionType."""
from typing import TYPE_CHECKING, Final, Literal, Optional, Union
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class ReactionType(TelegramObject):
"""Base class for Telegram ReactionType Objects.
There exist :class:`telegram.ReactionTypeEmoji`, :class:`telegram.ReactionTypeCustomEmoji`
and :class:`telegram.ReactionTypePaid`.
.. versionadded:: 20.8
.. versionchanged:: 21.5
Added paid reaction.
Args:
type (:obj:`str`): Type of the reaction. Can be
:attr:`~telegram.ReactionType.EMOJI`, :attr:`~telegram.ReactionType.CUSTOM_EMOJI` or
:attr:`~telegram.ReactionType.PAID`.
Attributes:
type (:obj:`str`): Type of the reaction. Can be
:attr:`~telegram.ReactionType.EMOJI`, :attr:`~telegram.ReactionType.CUSTOM_EMOJI` or
:attr:`~telegram.ReactionType.PAID`.
"""
__slots__ = ("type",)
EMOJI: Final[constants.ReactionType] = constants.ReactionType.EMOJI
""":const:`telegram.constants.ReactionType.EMOJI`"""
CUSTOM_EMOJI: Final[constants.ReactionType] = constants.ReactionType.CUSTOM_EMOJI
""":const:`telegram.constants.ReactionType.CUSTOM_EMOJI`"""
PAID: Final[constants.ReactionType] = constants.ReactionType.PAID
""":const:`telegram.constants.ReactionType.PAID`
.. versionadded:: 21.5
"""
def __init__(
self,
type: Union[ # pylint: disable=redefined-builtin
Literal["emoji", "custom_emoji", "paid"], constants.ReactionType
],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required by all subclasses
self.type: str = enum.get_member(constants.ReactionType, type, type)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ReactionType"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
if not data and cls is ReactionType:
return None
_class_mapping: dict[str, type[ReactionType]] = {
cls.EMOJI: ReactionTypeEmoji,
cls.CUSTOM_EMOJI: ReactionTypeCustomEmoji,
cls.PAID: ReactionTypePaid,
}
if cls is ReactionType and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data, bot)
return super().de_json(data=data, bot=bot)
class ReactionTypeEmoji(ReactionType):
"""
Represents a reaction with a normal emoji.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if the :attr:`emoji` is equal.
.. versionadded:: 20.8
Args:
emoji (:obj:`str`): Reaction emoji. It can be one of
:const:`telegram.constants.ReactionEmoji`.
Attributes:
type (:obj:`str`): Type of the reaction,
always :tg-const:`telegram.ReactionType.EMOJI`.
emoji (:obj:`str`): Reaction emoji. It can be one of
:const:`telegram.constants.ReactionEmoji`.
"""
__slots__ = ("emoji",)
def __init__(
self,
emoji: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=ReactionType.EMOJI, api_kwargs=api_kwargs)
with self._unfrozen():
self.emoji: str = emoji
self._id_attrs = (self.emoji,)
class ReactionTypeCustomEmoji(ReactionType):
"""
Represents a reaction with a custom emoji.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if the :attr:`custom_emoji_id` is equal.
.. versionadded:: 20.8
Args:
custom_emoji_id (:obj:`str`): Custom emoji identifier.
Attributes:
type (:obj:`str`): Type of the reaction,
always :tg-const:`telegram.ReactionType.CUSTOM_EMOJI`.
custom_emoji_id (:obj:`str`): Custom emoji identifier.
"""
__slots__ = ("custom_emoji_id",)
def __init__(
self,
custom_emoji_id: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=ReactionType.CUSTOM_EMOJI, api_kwargs=api_kwargs)
with self._unfrozen():
self.custom_emoji_id: str = custom_emoji_id
self._id_attrs = (self.custom_emoji_id,)
class ReactionTypePaid(ReactionType):
"""
The reaction is paid.
.. versionadded:: 21.5
Attributes:
type (:obj:`str`): Type of the reaction,
always :tg-const:`telegram.ReactionType.PAID`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None):
super().__init__(type=ReactionType.PAID, api_kwargs=api_kwargs)
self._freeze()
class ReactionCount(TelegramObject):
"""This class represents a reaction added to a message along with the number of times it was
added.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if the :attr:`type` and :attr:`total_count` is equal.
.. versionadded:: 20.8
Args:
type (:class:`telegram.ReactionType`): Type of the reaction.
total_count (:obj:`int`): Number of times the reaction was added.
Attributes:
type (:class:`telegram.ReactionType`): Type of the reaction.
total_count (:obj:`int`): Number of times the reaction was added.
"""
__slots__ = (
"total_count",
"type",
)
def __init__(
self,
type: ReactionType, # pylint: disable=redefined-builtin
total_count: int,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.type: ReactionType = type
self.total_count: int = total_count
self._id_attrs = (
self.type,
self.total_count,
)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ReactionCount"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["type"] = ReactionType.de_json(data.get("type"), bot)
return super().de_json(data=data, bot=bot)
@@ -1,472 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 modules contains objects that represents Telegram Replies"""
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional, Union
from telegram._chat import Chat
from telegram._dice import Dice
from telegram._files.animation import Animation
from telegram._files.audio import Audio
from telegram._files.contact import Contact
from telegram._files.document import Document
from telegram._files.location import Location
from telegram._files.photosize import PhotoSize
from telegram._files.sticker import Sticker
from telegram._files.venue import Venue
from telegram._files.video import Video
from telegram._files.videonote import VideoNote
from telegram._files.voice import Voice
from telegram._games.game import Game
from telegram._giveaway import Giveaway, GiveawayWinners
from telegram._linkpreviewoptions import LinkPreviewOptions
from telegram._messageentity import MessageEntity
from telegram._messageorigin import MessageOrigin
from telegram._paidmedia import PaidMediaInfo
from telegram._payment.invoice import Invoice
from telegram._poll import Poll
from telegram._story import Story
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
if TYPE_CHECKING:
from telegram import Bot
class ExternalReplyInfo(TelegramObject):
"""
This object contains information about a message that is being replied to, which may
come from another chat or forum topic.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`origin` is equal.
.. versionadded:: 20.8
Args:
origin (:class:`telegram.MessageOrigin`): Origin of the message replied to by the given
message.
chat (:class:`telegram.Chat`, optional): Chat the original message belongs to. Available
only if the chat is a supergroup or a channel.
message_id (:obj:`int`, optional): Unique message identifier inside the original chat.
Available only if the original chat is a supergroup or a channel.
link_preview_options (:class:`telegram.LinkPreviewOptions`, optional): Options used for
link preview generation for the original message, if it is a text message
animation (:class:`telegram.Animation`, optional): Message is an animation, information
about the animation.
audio (:class:`telegram.Audio`, optional): Message is an audio file, information about the
file.
document (:class:`telegram.Document`, optional): Message is a general file, information
about the file.
photo (Sequence[:class:`telegram.PhotoSize`], optional): Message is a photo, available
sizes of the photo.
sticker (:class:`telegram.Sticker`, optional): Message is a sticker, information about the
sticker.
story (:class:`telegram.Story`, optional): Message is a forwarded story.
video (:class:`telegram.Video`, optional): Message is a video, information about the video.
video_note (:class:`telegram.VideoNote`, optional): Message is a `video note
<https://telegram.org/blog/video-messages-and-telescope>`_, information about the video
message.
voice (:class:`telegram.Voice`, optional): Message is a voice message, information about
the file.
has_media_spoiler (:obj:`bool`, optional): :obj:`True`, if the message media is covered by
a spoiler animation.
contact (:class:`telegram.Contact`, optional): Message is a shared contact, information
about the contact.
dice (:class:`telegram.Dice`, optional): Message is a dice with random value.
game (:Class:`telegram.Game`. optional): Message is a game, information about the game.
:ref:`More about games >> <games-tree>`.
giveaway (:class:`telegram.Giveaway`, optional): Message is a scheduled giveaway,
information about the giveaway.
giveaway_winners (:class:`telegram.GiveawayWinners`, optional): A giveaway with public
winners was completed.
invoice (:class:`telegram.Invoice`, optional): Message is an invoice for a payment,
information about the invoice. :ref:`More about payments >> <payments-tree>`.
location (:class:`telegram.Location`, optional): Message is a shared location, information
about the location.
poll (:class:`telegram.Poll`, optional): Message is a native poll, information about the
poll.
venue (:class:`telegram.Venue`, optional): Message is a venue, information about the venue.
paid_media (:class:`telegram.PaidMedia`, optional): Message contains paid media;
information about the paid media.
.. versionadded:: 21.4
Attributes:
origin (:class:`telegram.MessageOrigin`): Origin of the message replied to by the given
message.
chat (:class:`telegram.Chat`): Optional. Chat the original message belongs to. Available
only if the chat is a supergroup or a channel.
message_id (:obj:`int`): Optional. Unique message identifier inside the original chat.
Available only if the original chat is a supergroup or a channel.
link_preview_options (:class:`telegram.LinkPreviewOptions`): Optional. Options used for
link preview generation for the original message, if it is a text message.
animation (:class:`telegram.Animation`): Optional. Message is an animation, information
about the animation.
audio (:class:`telegram.Audio`): Optional. Message is an audio file, information about the
file.
document (:class:`telegram.Document`): Optional. Message is a general file, information
about the file.
photo (tuple[:class:`telegram.PhotoSize`]): Optional. Message is a photo, available sizes
of the photo.
sticker (:class:`telegram.Sticker`): Optional. Message is a sticker, information about the
sticker.
story (:class:`telegram.Story`): Optional. Message is a forwarded story.
video (:class:`telegram.Video`): Optional. Message is a video, information about the video.
video_note (:class:`telegram.VideoNote`): Optional. Message is a `video note
<https://telegram.org/blog/video-messages-and-telescope>`_, information about the video
message.
voice (:class:`telegram.Voice`): Optional. Message is a voice message, information about
the file.
has_media_spoiler (:obj:`bool`): Optional. :obj:`True`, if the message media is covered by
a spoiler animation.
contact (:class:`telegram.Contact`): Optional. Message is a shared contact, information
about the contact.
dice (:class:`telegram.Dice`): Optional. Message is a dice with random value.
game (:Class:`telegram.Game`): Optional. Message is a game, information about the game.
:ref:`More about games >> <games-tree>`.
giveaway (:class:`telegram.Giveaway`): Optional. Message is a scheduled giveaway,
information about the giveaway.
giveaway_winners (:class:`telegram.GiveawayWinners`): Optional. A giveaway with public
winners was completed.
invoice (:class:`telegram.Invoice`): Optional. Message is an invoice for a payment,
information about the invoice. :ref:`More about payments >> <payments-tree>`.
location (:class:`telegram.Location`): Optional. Message is a shared location, information
about the location.
poll (:class:`telegram.Poll`): Optional. Message is a native poll, information about the
poll.
venue (:class:`telegram.Venue`): Optional. Message is a venue, information about the venue.
paid_media (:class:`telegram.PaidMedia`): Optional. Message contains paid media;
information about the paid media.
.. versionadded:: 21.4
"""
__slots__ = (
"animation",
"audio",
"chat",
"contact",
"dice",
"document",
"game",
"giveaway",
"giveaway_winners",
"has_media_spoiler",
"invoice",
"link_preview_options",
"location",
"message_id",
"origin",
"paid_media",
"photo",
"poll",
"sticker",
"story",
"venue",
"video",
"video_note",
"voice",
)
def __init__(
self,
origin: MessageOrigin,
chat: Optional[Chat] = None,
message_id: Optional[int] = None,
link_preview_options: Optional[LinkPreviewOptions] = None,
animation: Optional[Animation] = None,
audio: Optional[Audio] = None,
document: Optional[Document] = None,
photo: Optional[Sequence[PhotoSize]] = None,
sticker: Optional[Sticker] = None,
story: Optional[Story] = None,
video: Optional[Video] = None,
video_note: Optional[VideoNote] = None,
voice: Optional[Voice] = None,
has_media_spoiler: Optional[bool] = None,
contact: Optional[Contact] = None,
dice: Optional[Dice] = None,
game: Optional[Game] = None,
giveaway: Optional[Giveaway] = None,
giveaway_winners: Optional[GiveawayWinners] = None,
invoice: Optional[Invoice] = None,
location: Optional[Location] = None,
poll: Optional[Poll] = None,
venue: Optional[Venue] = None,
paid_media: Optional[PaidMediaInfo] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.origin: MessageOrigin = origin
self.chat: Optional[Chat] = chat
self.message_id: Optional[int] = message_id
self.link_preview_options: Optional[LinkPreviewOptions] = link_preview_options
self.animation: Optional[Animation] = animation
self.audio: Optional[Audio] = audio
self.document: Optional[Document] = document
self.photo: Optional[tuple[PhotoSize, ...]] = parse_sequence_arg(photo)
self.sticker: Optional[Sticker] = sticker
self.story: Optional[Story] = story
self.video: Optional[Video] = video
self.video_note: Optional[VideoNote] = video_note
self.voice: Optional[Voice] = voice
self.has_media_spoiler: Optional[bool] = has_media_spoiler
self.contact: Optional[Contact] = contact
self.dice: Optional[Dice] = dice
self.game: Optional[Game] = game
self.giveaway: Optional[Giveaway] = giveaway
self.giveaway_winners: Optional[GiveawayWinners] = giveaway_winners
self.invoice: Optional[Invoice] = invoice
self.location: Optional[Location] = location
self.poll: Optional[Poll] = poll
self.venue: Optional[Venue] = venue
self.paid_media: Optional[PaidMediaInfo] = paid_media
self._id_attrs = (self.origin,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ExternalReplyInfo"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["origin"] = MessageOrigin.de_json(data.get("origin"), bot)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["link_preview_options"] = LinkPreviewOptions.de_json(
data.get("link_preview_options"), bot
)
data["animation"] = Animation.de_json(data.get("animation"), bot)
data["audio"] = Audio.de_json(data.get("audio"), bot)
data["document"] = Document.de_json(data.get("document"), bot)
data["photo"] = tuple(PhotoSize.de_list(data.get("photo"), bot))
data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
data["story"] = Story.de_json(data.get("story"), bot)
data["video"] = Video.de_json(data.get("video"), bot)
data["video_note"] = VideoNote.de_json(data.get("video_note"), bot)
data["voice"] = Voice.de_json(data.get("voice"), bot)
data["contact"] = Contact.de_json(data.get("contact"), bot)
data["dice"] = Dice.de_json(data.get("dice"), bot)
data["game"] = Game.de_json(data.get("game"), bot)
data["giveaway"] = Giveaway.de_json(data.get("giveaway"), bot)
data["giveaway_winners"] = GiveawayWinners.de_json(data.get("giveaway_winners"), bot)
data["invoice"] = Invoice.de_json(data.get("invoice"), bot)
data["location"] = Location.de_json(data.get("location"), bot)
data["poll"] = Poll.de_json(data.get("poll"), bot)
data["venue"] = Venue.de_json(data.get("venue"), bot)
data["paid_media"] = PaidMediaInfo.de_json(data.get("paid_media"), bot)
return super().de_json(data=data, bot=bot)
class TextQuote(TelegramObject):
"""
This object contains information about the quoted part of a message that is replied to
by the given message.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text` and :attr:`position` are equal.
.. versionadded:: 20.8
Args:
text (:obj:`str`): Text of the quoted part of a message that is replied to by the given
message.
position (:obj:`int`): Approximate quote position in the original message in UTF-16 code
units as specified by the sender.
entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities that
appear
in the quote. Currently, only bold, italic, underline, strikethrough, spoiler, and
custom_emoji entities are kept in quotes.
is_manual (:obj:`bool`, optional): :obj:`True`, if the quote was chosen manually by the
message sender. Otherwise, the quote was added automatically by the server.
Attributes:
text (:obj:`str`): Text of the quoted part of a message that is replied to by the given
message.
position (:obj:`int`): Approximate quote position in the original message in UTF-16 code
units as specified by the sender.
entities (tuple[:class:`telegram.MessageEntity`]): Optional. Special entities that appear
in the quote. Currently, only bold, italic, underline, strikethrough, spoiler, and
custom_emoji entities are kept in quotes.
is_manual (:obj:`bool`): Optional. :obj:`True`, if the quote was chosen manually by the
message sender. Otherwise, the quote was added automatically by the server.
"""
__slots__ = (
"entities",
"is_manual",
"position",
"text",
)
def __init__(
self,
text: str,
position: int,
entities: Optional[Sequence[MessageEntity]] = None,
is_manual: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.text: str = text
self.position: int = position
self.entities: Optional[tuple[MessageEntity, ...]] = parse_sequence_arg(entities)
self.is_manual: Optional[bool] = is_manual
self._id_attrs = (
self.text,
self.position,
)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TextQuote"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["entities"] = tuple(MessageEntity.de_list(data.get("entities"), bot))
return super().de_json(data=data, bot=bot)
class ReplyParameters(TelegramObject):
"""
Describes reply parameters for the message that is being sent.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`message_id` is equal.
.. versionadded:: 20.8
Args:
message_id (:obj:`int`): Identifier of the message that will be replied to in the current
chat, or in the chat :paramref:`chat_id` if it is specified.
chat_id (:obj:`int` | :obj:`str`, optional): If the message to be replied to is from a
different chat, |chat_id_channel|
Not supported for messages sent on behalf of a business account.
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| Can be
used only for replies in the same chat and forum topic.
quote (:obj:`str`, optional): Quoted part of the message to be replied to; 0-1024
characters after entities parsing. The quote must be an exact substring of the message
to be replied to, including bold, italic, underline, strikethrough, spoiler, and
custom_emoji entities. The message will fail to send if the quote isn't found in the
original message.
quote_parse_mode (:obj:`str`, optional): Mode for parsing entities in the quote. See
:wiki:`formatting options <Code-snippets#message-formatting-bold-italic-code->` for
more details.
quote_entities (Sequence[:class:`telegram.MessageEntity`], optional): A JSON-serialized
list
of special entities that appear in the quote. It can be specified instead of
:paramref:`quote_parse_mode`.
quote_position (:obj:`int`, optional): Position of the quote in the original message in
UTF-16 code units.
Attributes:
message_id (:obj:`int`): Identifier of the message that will be replied to in the current
chat, or in the chat :paramref:`chat_id` if it is specified.
chat_id (:obj:`int` | :obj:`str`): Optional. If the message to be replied to is from a
different chat, |chat_id_channel|
Not supported for messages sent on behalf of a business account.
allow_sending_without_reply (:obj:`bool`): Optional. |allow_sending_without_reply| Can be
used only for replies in the same chat and forum topic.
quote (:obj:`str`): Optional. Quoted part of the message to be replied to; 0-1024
characters after entities parsing. The quote must be an exact substring of the message
to be replied to, including bold, italic, underline, strikethrough, spoiler, and
custom_emoji entities. The message will fail to send if the quote isn't found in the
original message.
quote_parse_mode (:obj:`str`): Optional. Mode for parsing entities in the quote. See
:wiki:`formatting options <Code-snippets#message-formatting-bold-italic-code->` for
more details.
quote_entities (tuple[:class:`telegram.MessageEntity`]): Optional. A JSON-serialized list
of special entities that appear in the quote. It can be specified instead of
:paramref:`quote_parse_mode`.
quote_position (:obj:`int`): Optional. Position of the quote in the original message in
UTF-16 code units.
"""
__slots__ = (
"allow_sending_without_reply",
"chat_id",
"message_id",
"quote",
"quote_entities",
"quote_parse_mode",
"quote_position",
)
def __init__(
self,
message_id: int,
chat_id: Optional[Union[int, str]] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
quote: Optional[str] = None,
quote_parse_mode: ODVInput[str] = DEFAULT_NONE,
quote_entities: Optional[Sequence[MessageEntity]] = None,
quote_position: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.message_id: int = message_id
self.chat_id: Optional[Union[int, str]] = chat_id
self.allow_sending_without_reply: ODVInput[bool] = allow_sending_without_reply
self.quote: Optional[str] = quote
self.quote_parse_mode: ODVInput[str] = quote_parse_mode
self.quote_entities: Optional[tuple[MessageEntity, ...]] = parse_sequence_arg(
quote_entities
)
self.quote_position: Optional[int] = quote_position
self._id_attrs = (self.message_id,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ReplyParameters"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["quote_entities"] = tuple(MessageEntity.de_list(data.get("quote_entities"), bot))
return super().de_json(data=data, bot=bot)
@@ -1,360 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 ReplyKeyboardMarkup."""
from collections.abc import Sequence
from typing import Final, Optional, Union
from telegram import constants
from telegram._keyboardbutton import KeyboardButton
from telegram._telegramobject import TelegramObject
from telegram._utils.markup import check_keyboard_type
from telegram._utils.types import JSONDict
class ReplyKeyboardMarkup(TelegramObject):
"""This object represents a custom keyboard with reply options. Not supported in channels and
for messages sent on behalf of a Telegram Business account.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their size of :attr:`keyboard` and all the buttons are equal.
.. figure:: https://core.telegram.org/file/464001950/1191a/2RwpmgU-swU.123554/b5\
0478c124d5914c23
:align: center
A reply keyboard with reply options.
.. seealso::
Another kind of keyboard would be the :class:`telegram.InlineKeyboardMarkup`.
Examples:
* Example usage: A user requests to change the bot's language, bot replies to the request
with a keyboard to select the new language. Other users in the group don't see
the keyboard.
* :any:`Conversation Bot <examples.conversationbot>`
* :any:`Conversation Bot 2 <examples.conversationbot2>`
Args:
keyboard (Sequence[Sequence[:obj:`str` | :class:`telegram.KeyboardButton`]]): Array of
button rows, each represented by an Array of :class:`telegram.KeyboardButton` objects.
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard vertically
for optimal fit (e.g., make the keyboard smaller if there are just two rows of
buttons). Defaults to :obj:`False`, in which case the custom keyboard is always of the
same height as the app's standard keyboard.
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as soon as
it's been used. The keyboard will still be available, but clients will automatically
display the usual letter-keyboard in the chat - the user can press a special button in
the input field to see the custom keyboard again. Defaults to :obj:`False`.
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard to
specific users only. Targets:
1) Users that are @mentioned in the :attr:`~telegram.Message.text` of the
:class:`telegram.Message` object.
2) If the bot's message is a reply to a message in the same chat and forum topic,
sender of the original message.
Defaults to :obj:`False`.
input_field_placeholder (:obj:`str`, optional): The placeholder to be shown in the input
field when the keyboard is active;
:tg-const:`telegram.ReplyKeyboardMarkup.MIN_INPUT_FIELD_PLACEHOLDER`-
:tg-const:`telegram.ReplyKeyboardMarkup.MAX_INPUT_FIELD_PLACEHOLDER`
characters.
.. versionadded:: 13.7
is_persistent (:obj:`bool`, optional): Requests clients to always show the keyboard when
the regular keyboard is hidden. Defaults to :obj:`False`, in which case the custom
keyboard can be hidden and opened with a keyboard icon.
.. versionadded:: 20.0
Attributes:
keyboard (tuple[tuple[:class:`telegram.KeyboardButton`]]): Array of button rows,
each represented by an Array of :class:`telegram.KeyboardButton` objects.
resize_keyboard (:obj:`bool`): Optional. Requests clients to resize the keyboard vertically
for optimal fit (e.g., make the keyboard smaller if there are just two rows of
buttons). Defaults to :obj:`False`, in which case the custom keyboard is always of the
same height as the app's standard keyboard.
one_time_keyboard (:obj:`bool`): Optional. Requests clients to hide the keyboard as soon as
it's been used. The keyboard will still be available, but clients will automatically
display the usual letter-keyboard in the chat - the user can press a special button in
the input field to see the custom keyboard again. Defaults to :obj:`False`.
selective (:obj:`bool`): Optional. Show the keyboard to specific users only.
Targets:
1) Users that are @mentioned in the :attr:`~telegram.Message.text` of the
:class:`telegram.Message` object.
2) If the bot's message is a reply to a message in the same chat and forum topic,
sender of the original message.
Defaults to :obj:`False`.
input_field_placeholder (:obj:`str`): Optional. The placeholder to be shown in the input
field when the keyboard is active;
:tg-const:`telegram.ReplyKeyboardMarkup.MIN_INPUT_FIELD_PLACEHOLDER`-
:tg-const:`telegram.ReplyKeyboardMarkup.MAX_INPUT_FIELD_PLACEHOLDER`
characters.
.. versionadded:: 13.7
is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard when
the regular keyboard is hidden. If :obj:`False`, the custom keyboard can be hidden and
opened with a keyboard icon.
.. versionadded:: 20.0
"""
__slots__ = (
"input_field_placeholder",
"is_persistent",
"keyboard",
"one_time_keyboard",
"resize_keyboard",
"selective",
)
def __init__(
self,
keyboard: Sequence[Sequence[Union[str, KeyboardButton]]],
resize_keyboard: Optional[bool] = None,
one_time_keyboard: Optional[bool] = None,
selective: Optional[bool] = None,
input_field_placeholder: Optional[str] = None,
is_persistent: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
if not check_keyboard_type(keyboard):
raise ValueError(
"The parameter `keyboard` should be a sequence of sequences of "
"strings or KeyboardButtons"
)
# Required
self.keyboard: tuple[tuple[KeyboardButton, ...], ...] = tuple(
tuple(KeyboardButton(button) if isinstance(button, str) else button for button in row)
for row in keyboard
)
# Optionals
self.resize_keyboard: Optional[bool] = resize_keyboard
self.one_time_keyboard: Optional[bool] = one_time_keyboard
self.selective: Optional[bool] = selective
self.input_field_placeholder: Optional[str] = input_field_placeholder
self.is_persistent: Optional[bool] = is_persistent
self._id_attrs = (self.keyboard,)
self._freeze()
@classmethod
def from_button(
cls,
button: Union[KeyboardButton, str],
resize_keyboard: bool = False,
one_time_keyboard: bool = False,
selective: bool = False,
input_field_placeholder: Optional[str] = None,
is_persistent: Optional[bool] = None,
**kwargs: object,
) -> "ReplyKeyboardMarkup":
"""Shortcut for::
ReplyKeyboardMarkup([[button]], **kwargs)
Return a ReplyKeyboardMarkup from a single KeyboardButton.
Args:
button (:class:`telegram.KeyboardButton` | :obj:`str`): The button to use in
the markup.
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard
vertically for optimal fit (e.g., make the keyboard smaller if there are just two
rows of buttons). Defaults to :obj:`False`, in which case the custom keyboard is
always of the same height as the app's standard keyboard.
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as
soon as it's been used. The keyboard will still be available, but clients will
automatically display the usual letter-keyboard in the chat - the user can press
a special button in the input field to see the custom keyboard again.
Defaults to :obj:`False`.
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard
to specific users only. Targets:
1) Users that are @mentioned in the text of the Message object.
2) If the bot's message is a reply to a message in the same chat and forum topic,
sender of the original message.
Defaults to :obj:`False`.
input_field_placeholder (:obj:`str`): Optional. The placeholder shown in the input
field when the reply is active.
.. versionadded:: 13.7
is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard
when the regular keyboard is hidden. Defaults to :obj:`False`, in which case the
custom keyboard can be hidden and opened with a keyboard icon.
.. versionadded:: 20.0
"""
return cls(
[[button]],
resize_keyboard=resize_keyboard,
one_time_keyboard=one_time_keyboard,
selective=selective,
input_field_placeholder=input_field_placeholder,
is_persistent=is_persistent,
**kwargs, # type: ignore[arg-type]
)
@classmethod
def from_row(
cls,
button_row: Sequence[Union[str, KeyboardButton]],
resize_keyboard: bool = False,
one_time_keyboard: bool = False,
selective: bool = False,
input_field_placeholder: Optional[str] = None,
is_persistent: Optional[bool] = None,
**kwargs: object,
) -> "ReplyKeyboardMarkup":
"""Shortcut for::
ReplyKeyboardMarkup([button_row], **kwargs)
Return a ReplyKeyboardMarkup from a single row of KeyboardButtons.
Args:
button_row (Sequence[:class:`telegram.KeyboardButton` | :obj:`str`]): The button to
use in the markup.
.. versionchanged:: 20.0
|sequenceargs|
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard
vertically for optimal fit (e.g., make the keyboard smaller if there are just two
rows of buttons). Defaults to :obj:`False`, in which case the custom keyboard is
always of the same height as the app's standard keyboard.
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as
soon as it's been used. The keyboard will still be available, but clients will
automatically display the usual letter-keyboard in the chat - the user can press
a special button in the input field to see the custom keyboard again.
Defaults to :obj:`False`.
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard
to specific users only. Targets:
1) Users that are @mentioned in the text of the Message object.
2) If the bot's message is a reply to a message in the same chat and forum topic,
sender of the original message.
Defaults to :obj:`False`.
input_field_placeholder (:obj:`str`): Optional. The placeholder shown in the input
field when the reply is active.
.. versionadded:: 13.7
is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard
when the regular keyboard is hidden. Defaults to :obj:`False`, in which case the
custom keyboard can be hidden and opened with a keyboard icon.
.. versionadded:: 20.0
"""
return cls(
[button_row],
resize_keyboard=resize_keyboard,
one_time_keyboard=one_time_keyboard,
selective=selective,
input_field_placeholder=input_field_placeholder,
is_persistent=is_persistent,
**kwargs, # type: ignore[arg-type]
)
@classmethod
def from_column(
cls,
button_column: Sequence[Union[str, KeyboardButton]],
resize_keyboard: bool = False,
one_time_keyboard: bool = False,
selective: bool = False,
input_field_placeholder: Optional[str] = None,
is_persistent: Optional[bool] = None,
**kwargs: object,
) -> "ReplyKeyboardMarkup":
"""Shortcut for::
ReplyKeyboardMarkup([[button] for button in button_column], **kwargs)
Return a ReplyKeyboardMarkup from a single column of KeyboardButtons.
Args:
button_column (Sequence[:class:`telegram.KeyboardButton` | :obj:`str`]): The button
to use in the markup.
.. versionchanged:: 20.0
|sequenceargs|
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard
vertically for optimal fit (e.g., make the keyboard smaller if there are just two
rows of buttons). Defaults to :obj:`False`, in which case the custom keyboard is
always of the same height as the app's standard keyboard.
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as
soon as it's been used. The keyboard will still be available, but clients will
automatically display the usual letter-keyboard in the chat - the user can press
a special button in the input field to see the custom keyboard again.
Defaults to :obj:`False`.
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard
to specific users only. Targets:
1) Users that are @mentioned in the text of the Message object.
2) If the bot's message is a reply to a message in the same chat and forum topic,
sender of the original message.
Defaults to :obj:`False`.
input_field_placeholder (:obj:`str`): Optional. The placeholder shown in the input
field when the reply is active.
.. versionadded:: 13.7
is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard
when the regular keyboard is hidden. Defaults to :obj:`False`, in which case the
custom keyboard can be hidden and opened with a keyboard icon.
.. versionadded:: 20.0
"""
button_grid = [[button] for button in button_column]
return cls(
button_grid,
resize_keyboard=resize_keyboard,
one_time_keyboard=one_time_keyboard,
selective=selective,
input_field_placeholder=input_field_placeholder,
is_persistent=is_persistent,
**kwargs, # type: ignore[arg-type]
)
MIN_INPUT_FIELD_PLACEHOLDER: Final[int] = constants.ReplyLimit.MIN_INPUT_FIELD_PLACEHOLDER
""":const:`telegram.constants.ReplyLimit.MIN_INPUT_FIELD_PLACEHOLDER`
.. versionadded:: 20.0
"""
MAX_INPUT_FIELD_PLACEHOLDER: Final[int] = constants.ReplyLimit.MAX_INPUT_FIELD_PLACEHOLDER
""":const:`telegram.constants.ReplyLimit.MAX_INPUT_FIELD_PLACEHOLDER`
.. versionadded:: 20.0
"""
@@ -1,73 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 ReplyKeyboardRemove."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class ReplyKeyboardRemove(TelegramObject):
"""
Upon receiving a message with this object, Telegram clients will remove the current custom
keyboard and display the default letter-keyboard. By default, custom keyboards are displayed
until a new keyboard is sent by a bot. An exception is made for one-time keyboards that are
hidden immediately after the user presses a button (see :class:`telegram.ReplyKeyboardMarkup`).
Not supported in channels and for messages sent on behalf of a Telegram Business account.
Note:
User will not be able to summon this keyboard; if you want to hide the keyboard from
sight but keep it accessible, use :attr:`telegram.ReplyKeyboardMarkup.one_time_keyboard`.
Examples:
* Example usage: A user votes in a poll, bot returns confirmation message in reply to
the vote and removes the keyboard for that user, while still showing the keyboard with
poll options to users who haven't voted yet.
* :any:`Conversation Bot <examples.conversationbot>`
* :any:`Conversation Bot 2 <examples.conversationbot2>`
Args:
selective (:obj:`bool`, optional): Use this parameter if you want to remove the keyboard
for specific users only. Targets:
1) Users that are @mentioned in the text of the :class:`telegram.Message` object.
2) If the bot's message is a reply to a message in the same chat and forum topic,
sender of the original message.
Attributes:
remove_keyboard (:obj:`True`): Requests clients to remove the custom keyboard.
selective (:obj:`bool`): Optional. Remove the keyboard for specific users only.
Targets:
1) Users that are @mentioned in the text of the :class:`telegram.Message` object.
2) If the bot's message is a reply to a message in the same chat and forum topic,
sender of the original message.
"""
__slots__ = ("remove_keyboard", "selective")
def __init__(self, selective: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None):
super().__init__(api_kwargs=api_kwargs)
# Required
self.remove_keyboard: bool = True
# Optionals
self.selective: Optional[bool] = selective
self._freeze()
@@ -1,56 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 Sent Web App Message."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class SentWebAppMessage(TelegramObject):
"""Contains information about an inline message sent by a Web App on behalf of a user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`inline_message_id` are equal.
.. versionadded:: 20.0
Args:
inline_message_id (:obj:`str`, optional): Identifier of the sent inline message. Available
only if there is an :attr:`inline keyboard <telegram.InlineKeyboardMarkup>` attached to
the message.
Attributes:
inline_message_id (:obj:`str`): Optional. Identifier of the sent inline message. Available
only if there is an :attr:`inline keyboard <telegram.InlineKeyboardMarkup>` attached to
the message.
"""
__slots__ = ("inline_message_id",)
def __init__(
self, inline_message_id: Optional[str] = None, *, api_kwargs: Optional[JSONDict] = None
):
super().__init__(api_kwargs=api_kwargs)
# Optionals
self.inline_message_id: Optional[str] = inline_message_id
self._id_attrs = (self.inline_message_id,)
self._freeze()
@@ -1,268 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 two objects used for request chats/users service messages."""
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram._bot import Bot
class UsersShared(TelegramObject):
"""
This object contains information about the user whose identifier was shared with the bot
using a :class:`telegram.KeyboardButtonRequestUsers` button.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_id` and :attr:`users` are equal.
.. versionadded:: 20.8
Bot API 7.0 replaces ``UserShared`` with this class. The only difference is that now
the ``user_ids`` is a sequence instead of a single integer.
.. versionchanged:: 21.1
The argument :attr:`users` is now considered for the equality comparison instead of
``user_ids``.
.. versionremoved:: 21.2
Removed the deprecated argument and attribute ``user_ids``.
Args:
request_id (:obj:`int`): Identifier of the request.
users (Sequence[:class:`telegram.SharedUser`]): Information about users shared with the
bot.
.. versionadded:: 21.1
.. versionchanged:: 21.2
This argument is now required.
Attributes:
request_id (:obj:`int`): Identifier of the request.
users (tuple[:class:`telegram.SharedUser`]): Information about users shared with the
bot.
.. versionadded:: 21.1
"""
__slots__ = ("request_id", "users")
def __init__(
self,
request_id: int,
users: Sequence["SharedUser"],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.request_id: int = request_id
self.users: tuple[SharedUser, ...] = parse_sequence_arg(users)
self._id_attrs = (self.request_id, self.users)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["UsersShared"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["users"] = SharedUser.de_list(data.get("users"), bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
if user_ids := data.get("user_ids"):
api_kwargs = {"user_ids": user_ids}
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
class ChatShared(TelegramObject):
"""
This object contains information about the chat whose identifier was shared with the bot
using a :class:`telegram.KeyboardButtonRequestChat` button.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_id` and :attr:`chat_id` are equal.
.. versionadded:: 20.1
Args:
request_id (:obj:`int`): Identifier of the request.
chat_id (:obj:`int`): Identifier of the shared user. This number may be greater than 32
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it is smaller than 52 bits, so a signed 64-bit integer or double-precision
float type are safe for storing this identifier.
title (:obj:`str`, optional): Title of the chat, if the title was requested by the bot.
.. versionadded:: 21.1
username (:obj:`str`, optional): Username of the chat, if the username was requested by
the bot and available.
.. versionadded:: 21.1
photo (Sequence[:class:`telegram.PhotoSize`], optional): Available sizes of the chat photo,
if the photo was requested by the bot
.. versionadded:: 21.1
Attributes:
request_id (:obj:`int`): Identifier of the request.
chat_id (:obj:`int`): Identifier of the shared user. This number may be greater than 32
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it is smaller than 52 bits, so a signed 64-bit integer or double-precision
float type are safe for storing this identifier.
title (:obj:`str`): Optional. Title of the chat, if the title was requested by the bot.
.. versionadded:: 21.1
username (:obj:`str`): Optional. Username of the chat, if the username was requested by
the bot and available.
.. versionadded:: 21.1
photo (tuple[:class:`telegram.PhotoSize`]): Optional. Available sizes of the chat photo,
if the photo was requested by the bot
.. versionadded:: 21.1
"""
__slots__ = ("chat_id", "photo", "request_id", "title", "username")
def __init__(
self,
request_id: int,
chat_id: int,
title: Optional[str] = None,
username: Optional[str] = None,
photo: Optional[Sequence[PhotoSize]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.request_id: int = request_id
self.chat_id: int = chat_id
self.title: Optional[str] = title
self.username: Optional[str] = username
self.photo: Optional[tuple[PhotoSize, ...]] = parse_sequence_arg(photo)
self._id_attrs = (self.request_id, self.chat_id)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatShared"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
return super().de_json(data=data, bot=bot)
class SharedUser(TelegramObject):
"""
This object contains information about a user that was shared with the bot using a
:class:`telegram.KeyboardButtonRequestUsers` button.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`user_id` is equal.
.. versionadded:: 21.1
Args:
user_id (:obj:`int`): Identifier of the shared user. This number may have 32 significant
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it has atmost 52 significant bits, so 64-bit integers or double-precision
float types are safe for storing these identifiers. The bot may not have access to the
user and could be unable to use this identifier, unless the user is already known to
the bot by some other means.
first_name (:obj:`str`, optional): First name of the user, if the name was requested by the
bot.
last_name (:obj:`str`, optional): Last name of the user, if the name was requested by the
bot.
username (:obj:`str`, optional): Username of the user, if the username was requested by the
bot.
photo (Sequence[:class:`telegram.PhotoSize`], optional): Available sizes of the chat photo,
if the photo was requested by the bot.
Attributes:
user_id (:obj:`int`): Identifier of the shared user. This number may have 32 significant
bits and some programming languages may have difficulty/silent defects in interpreting
it. But it has atmost 52 significant bits, so 64-bit integers or double-precision
float types are safe for storing these identifiers. The bot may not have access to the
user and could be unable to use this identifier, unless the user is already known to
the bot by some other means.
first_name (:obj:`str`): Optional. First name of the user, if the name was requested by the
bot.
last_name (:obj:`str`): Optional. Last name of the user, if the name was requested by the
bot.
username (:obj:`str`): Optional. Username of the user, if the username was requested by the
bot.
photo (tuple[:class:`telegram.PhotoSize`]): Available sizes of the chat photo, if
the photo was requested by the bot. This list is empty if the photo was not requsted.
"""
__slots__ = ("first_name", "last_name", "photo", "user_id", "username")
def __init__(
self,
user_id: int,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
username: Optional[str] = None,
photo: Optional[Sequence[PhotoSize]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.user_id: int = user_id
self.first_name: Optional[str] = first_name
self.last_name: Optional[str] = last_name
self.username: Optional[str] = username
self.photo: Optional[tuple[PhotoSize, ...]] = parse_sequence_arg(photo)
self._id_attrs = (self.user_id,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["SharedUser"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
return super().de_json(data=data, bot=bot)
@@ -1,82 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 related to a Telegram Story."""
from typing import TYPE_CHECKING, Optional
from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class Story(TelegramObject):
"""
This object represents a story.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`chat` and :attr:`id` are equal.
.. versionadded:: 20.5
.. versionchanged:: 21.0
Added attributes :attr:`chat` and :attr:`id` and equality based on them.
Args:
chat (:class:`telegram.Chat`): Chat that posted the story.
id (:obj:`int`): Unique identifier for the story in the chat.
Attributes:
chat (:class:`telegram.Chat`): Chat that posted the story.
id (:obj:`int`): Unique identifier for the story in the chat.
"""
__slots__ = (
"chat",
"id",
)
def __init__(
self,
chat: Chat,
id: int, # pylint: disable=redefined-builtin
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.chat: Chat = chat
self.id: int = id
self._id_attrs = (self.chat, self.id)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Story"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["chat"] = Chat.de_json(data.get("chat", {}), bot)
return super().de_json(data=data, bot=bot)
@@ -1,100 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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
"""This module contains a class that represents a Telegram SwitchInlineQueryChosenChat."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class SwitchInlineQueryChosenChat(TelegramObject):
"""
This object represents an inline button that switches the current user to inline mode in a
chosen chat, with an optional default inline query.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`query`, :attr:`allow_user_chats`, :attr:`allow_bot_chats`,
:attr:`allow_group_chats`, and :attr:`allow_channel_chats` are equal.
.. versionadded:: 20.3
Caution:
The PTB team has discovered that you must pass at least one of
:paramref:`allow_user_chats`, :paramref:`allow_bot_chats`, :paramref:`allow_group_chats`,
or :paramref:`allow_channel_chats` to Telegram. Otherwise, an error will be raised.
Args:
query (:obj:`str`, optional): The default inline query to be inserted in the input field.
If left empty, only the bot's username will be inserted.
allow_user_chats (:obj:`bool`, optional): Pass :obj:`True`, if private chats with users
can be chosen.
allow_bot_chats (:obj:`bool`, optional): Pass :obj:`True`, if private chats with bots can
be chosen.
allow_group_chats (:obj:`bool`, optional): Pass :obj:`True`, if group and supergroup chats
can be chosen.
allow_channel_chats (:obj:`bool`, optional): Pass :obj:`True`, if channel chats can be
chosen.
Attributes:
query (:obj:`str`): Optional. The default inline query to be inserted in the input field.
If left empty, only the bot's username will be inserted.
allow_user_chats (:obj:`bool`): Optional. :obj:`True`, if private chats with users can be
chosen.
allow_bot_chats (:obj:`bool`): Optional. :obj:`True`, if private chats with bots can be
chosen.
allow_group_chats (:obj:`bool`): Optional. :obj:`True`, if group and supergroup chats can
be chosen.
allow_channel_chats (:obj:`bool`): Optional. :obj:`True`, if channel chats can be chosen.
"""
__slots__ = (
"allow_bot_chats",
"allow_channel_chats",
"allow_group_chats",
"allow_user_chats",
"query",
)
def __init__(
self,
query: Optional[str] = None,
allow_user_chats: Optional[bool] = None,
allow_bot_chats: Optional[bool] = None,
allow_group_chats: Optional[bool] = None,
allow_channel_chats: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Optional
self.query: Optional[str] = query
self.allow_user_chats: Optional[bool] = allow_user_chats
self.allow_bot_chats: Optional[bool] = allow_bot_chats
self.allow_group_chats: Optional[bool] = allow_group_chats
self.allow_channel_chats: Optional[bool] = allow_channel_chats
self._id_attrs = (
self.query,
self.allow_user_chats,
self.allow_bot_chats,
self.allow_group_chats,
self.allow_channel_chats,
)
self._freeze()
@@ -1,675 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].
"""Base class for Telegram Objects."""
import contextlib
import datetime as dtm
import inspect
import json
from collections.abc import Iterator, Mapping, Sized
from contextlib import contextmanager
from copy import deepcopy
from itertools import chain
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, ClassVar, Optional, TypeVar, Union, cast
from telegram._utils.datetime import to_timestamp
from telegram._utils.defaultvalue import DefaultValue
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
if TYPE_CHECKING:
from telegram import Bot
Tele_co = TypeVar("Tele_co", bound="TelegramObject", covariant=True)
class TelegramObject:
"""Base class for most Telegram objects.
Objects of this type are subscriptable with strings. See :meth:`__getitem__` for more details.
The :mod:`pickle` and :func:`~copy.deepcopy` behavior of objects of this type are defined by
:meth:`__getstate__`, :meth:`__setstate__` and :meth:`__deepcopy__`.
Tip:
Objects of this type can be serialized via Python's :mod:`pickle` module and pickled
objects from one version of PTB are usually loadable in future versions. However, we can
not guarantee that this compatibility will always be provided. At least a manual one-time
conversion of the data may be needed on major updates of the library.
.. versionchanged:: 20.0
* Removed argument and attribute ``bot`` for several subclasses. Use
:meth:`set_bot` and :meth:`get_bot` instead.
* Removed the possibility to pass arbitrary keyword arguments for several subclasses.
* String representations objects of this type was overhauled. See :meth:`__repr__` for
details. As this class doesn't implement :meth:`object.__str__`, the default
implementation will be used, which is equivalent to :meth:`__repr__`.
* Objects of this class (or subclasses) are now immutable. This means that you can't set
or delete attributes anymore. Moreover, attributes that were formerly of type
:obj:`list` are now of type :obj:`tuple`.
Arguments:
api_kwargs (dict[:obj:`str`, any], optional): |toapikwargsarg|
.. versionadded:: 20.0
Attributes:
api_kwargs (:obj:`types.MappingProxyType` [:obj:`str`, any]): |toapikwargsattr|
.. versionadded:: 20.0
"""
__slots__ = ("_bot", "_frozen", "_id_attrs", "api_kwargs")
# Used to cache the names of the parameters of the __init__ method of the class
# Must be a private attribute to avoid name clashes between subclasses
__INIT_PARAMS: ClassVar[set[str]] = set()
# Used to check if __INIT_PARAMS has been set for the current class. Unfortunately, we can't
# just check if `__INIT_PARAMS is None`, since subclasses use the parent class' __INIT_PARAMS
# unless it's overridden
__INIT_PARAMS_CHECK: Optional[type["TelegramObject"]] = None
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
# Setting _frozen to `False` here means that classes without arguments still need to
# implement __init__. However, with `True` would mean increased usage of
# `with self._unfrozen()` in the `__init__` of subclasses and we have fewer empty
# classes than classes with arguments.
self._frozen: bool = False
self._id_attrs: tuple[object, ...] = ()
self._bot: Optional[Bot] = None
# We don't do anything with api_kwargs here - see docstring of _apply_api_kwargs
self.api_kwargs: Mapping[str, Any] = MappingProxyType(api_kwargs or {})
def __eq__(self, other: object) -> bool:
"""Compares this object with :paramref:`other` in terms of equality.
If this object and :paramref:`other` are `not` objects of the same class,
this comparison will fall back to Python's default implementation of :meth:`object.__eq__`.
Otherwise, both objects may be compared in terms of equality, if the corresponding
subclass of :class:`TelegramObject` has defined a set of attributes to compare and
the objects are considered to be equal, if all of these attributes are equal.
If the subclass has not defined a set of attributes to compare, a warning will be issued.
Tip:
If instances of a class in the :mod:`telegram` module are comparable in terms of
equality, the documentation of the class will state the attributes that will be used
for this comparison.
Args:
other (:obj:`object`): The object to compare with.
Returns:
:obj:`bool`
"""
if isinstance(other, self.__class__):
if not self._id_attrs:
warn(
f"Objects of type {self.__class__.__name__} can not be meaningfully tested for"
" equivalence.",
stacklevel=2,
)
if not other._id_attrs:
warn(
f"Objects of type {other.__class__.__name__} can not be meaningfully tested"
" for equivalence.",
stacklevel=2,
)
return self._id_attrs == other._id_attrs
return super().__eq__(other)
def __hash__(self) -> int:
"""Builds a hash value for this object such that the hash of two objects is equal if and
only if the objects are equal in terms of :meth:`__eq__`.
Returns:
:obj:`int`
"""
if self._id_attrs:
return hash((self.__class__, self._id_attrs))
return super().__hash__()
def __setattr__(self, key: str, value: object) -> None:
"""Overrides :meth:`object.__setattr__` to prevent the overriding of attributes.
Raises:
:exc:`AttributeError`
"""
# protected attributes can always be set for convenient internal use
if key[0] == "_" or not getattr(self, "_frozen", True):
super().__setattr__(key, value)
return
raise AttributeError(
f"Attribute `{key}` of class `{self.__class__.__name__}` can't be set!"
)
def __delattr__(self, key: str) -> None:
"""Overrides :meth:`object.__delattr__` to prevent the deletion of attributes.
Raises:
:exc:`AttributeError`
"""
# protected attributes can always be set for convenient internal use
if key[0] == "_" or not getattr(self, "_frozen", True):
super().__delattr__(key)
return
raise AttributeError(
f"Attribute `{key}` of class `{self.__class__.__name__}` can't be deleted!"
)
def __repr__(self) -> str:
"""Gives a string representation of this object in the form
``ClassName(attr_1=value_1, attr_2=value_2, ...)``, where attributes are omitted if they
have the value :obj:`None` or are empty instances of :class:`collections.abc.Sized` (e.g.
:class:`list`, :class:`dict`, :class:`set`, :class:`str`, etc.).
As this class doesn't implement :meth:`object.__str__`, the default implementation
will be used, which is equivalent to :meth:`__repr__`.
Returns:
:obj:`str`
"""
# * `__repr__` goal is to be unambiguous
# * `__str__` goal is to be readable
# * `str()` calls `__repr__`, if `__str__` is not defined
# In our case "unambiguous" and "readable" largely coincide, so we can use the same logic.
as_dict = self._get_attrs(recursive=False, include_private=False)
if not self.api_kwargs:
# Drop api_kwargs from the representation, if empty
as_dict.pop("api_kwargs", None)
else:
# Otherwise, we want to skip the "mappingproxy" part of the repr
as_dict["api_kwargs"] = dict(self.api_kwargs)
contents = ", ".join(
f"{k}={as_dict[k]!r}"
for k in sorted(as_dict.keys())
if (
as_dict[k] is not None
and not (
isinstance(as_dict[k], Sized)
and len(as_dict[k]) == 0 # type: ignore[arg-type]
)
)
)
return f"{self.__class__.__name__}({contents})"
def __getitem__(self, item: str) -> object:
"""
Objects of this type are subscriptable with strings, where
``telegram_object["attribute_name"]`` is equivalent to ``telegram_object.attribute_name``.
Tip:
This is useful for dynamic attribute lookup, i.e. ``telegram_object[arg]`` where the
value of ``arg`` is determined at runtime.
In all other cases, it's recommended to use the dot notation instead, i.e.
``telegram_object.attribute_name``.
.. versionchanged:: 20.0
``telegram_object['from']`` will look up the key ``from_user``. This is to account for
special cases like :attr:`Message.from_user` that deviate from the official Bot API.
Args:
item (:obj:`str`): The name of the attribute to look up.
Returns:
:obj:`object`
Raises:
:exc:`KeyError`: If the object does not have an attribute with the appropriate name.
"""
if item == "from":
item = "from_user"
try:
return getattr(self, item)
except AttributeError as exc:
raise KeyError(
f"Objects of type {self.__class__.__name__} don't have an attribute called "
f"`{item}`."
) from exc
def __getstate__(self) -> dict[str, Union[str, object]]:
"""
Overrides :meth:`object.__getstate__` to customize the pickling process of objects of this
type.
The returned state does `not` contain the :class:`telegram.Bot` instance set with
:meth:`set_bot` (if any), as it can't be pickled.
Returns:
state (dict[:obj:`str`, :obj:`object`]): The state of the object.
"""
out = self._get_attrs(
include_private=True, recursive=False, remove_bot=True, convert_default_vault=False
)
# MappingProxyType is not pickable, so we convert it to a dict and revert in
# __setstate__
out["api_kwargs"] = dict(self.api_kwargs)
return out
def __setstate__(self, state: dict[str, object]) -> None:
"""
Overrides :meth:`object.__setstate__` to customize the unpickling process of objects of
this type. Modifies the object in-place.
If any data was stored in the :attr:`api_kwargs` of the pickled object, this method checks
if the class now has dedicated attributes for those keys and moves the values from
:attr:`api_kwargs` to the dedicated attributes.
This can happen, if serialized data is loaded with a new version of this library, where
the new version was updated to account for updates of the Telegram Bot API.
If on the contrary an attribute was removed from the class, the value is not discarded but
made available via :attr:`api_kwargs`.
Args:
state (:obj:`dict`): The data to set as attributes of this object.
"""
self._unfreeze()
# Make sure that we have a `_bot` attribute. This is necessary, since __getstate__ omits
# this as Bots are not pickable.
self._bot = None
# get api_kwargs first because we may need to add entries to it (see try-except below)
api_kwargs = cast(dict[str, object], state.pop("api_kwargs", {}))
# get _frozen before the loop to avoid setting it to True in the loop
frozen = state.pop("_frozen", False)
for key, val in state.items():
try:
setattr(self, key, val)
except AttributeError:
# So an attribute was deprecated and removed from the class. Let's handle this:
# 1) Is the attribute now a property with no setter? Let's check that:
if isinstance(getattr(self.__class__, key, None), property):
# It is, so let's try to set the "private attribute" instead
try:
setattr(self, f"_{key}", val)
# If this fails as well, guess we've completely removed it. Let's add it to
# api_kwargs as fallback
except AttributeError:
api_kwargs[key] = val
# 2) The attribute is a private attribute, i.e. it went through case 1) in the past
elif key.startswith("_"):
continue # skip adding this to api_kwargs, the attribute is lost forever.
api_kwargs[key] = val # add it to api_kwargs as fallback
# For api_kwargs we first apply any kwargs that are already attributes of the object
# and then set the rest as MappingProxyType attribute. Converting to MappingProxyType
# is necessary, since __getstate__ converts it to a dict as MPT is not pickable.
self._apply_api_kwargs(api_kwargs)
self.api_kwargs = MappingProxyType(api_kwargs)
# Apply freezing if necessary
# we .get(…) the setting for backwards compatibility with objects that were pickled
# before the freeze feature was introduced
if frozen:
self._freeze()
def __deepcopy__(self: Tele_co, memodict: dict[int, object]) -> Tele_co:
"""
Customizes how :func:`copy.deepcopy` processes objects of this type.
The only difference to the default implementation is that the :class:`telegram.Bot`
instance set via :meth:`set_bot` (if any) is not copied, but shared between the original
and the copy, i.e.::
assert telegram_object.get_bot() is copy.deepcopy(telegram_object).get_bot()
Args:
memodict (:obj:`dict`): A dictionary that maps objects to their copies.
Returns:
:class:`telegram.TelegramObject`: The copied object.
"""
bot = self._bot # Save bot so we can set it after copying
self.set_bot(None) # set to None so it is not deepcopied
cls = self.__class__
result = cls.__new__(cls) # create a new instance
memodict[id(self)] = result # save the id of the object in the dict
result._frozen = False # unfreeze the new object for setting the attributes
# now we set the attributes in the deepcopied object
for k in self._get_attrs_names(include_private=True):
if k == "_frozen":
# Setting the frozen status to True would prevent the attributes from being set
continue
if k == "api_kwargs":
# Need to copy api_kwargs manually, since it's a MappingProxyType is not
# pickable and deepcopy uses the pickle interface
setattr(result, k, MappingProxyType(deepcopy(dict(self.api_kwargs), memodict)))
continue
try:
setattr(result, k, deepcopy(getattr(self, k), memodict))
except AttributeError:
# Skip missing attributes. This can happen if the object was loaded from a pickle
# file that was created with an older version of the library, where the class
# did not have the attribute yet.
continue
# Apply freezing if necessary
if self._frozen:
result._freeze()
result.set_bot(bot) # Assign the bots back
self.set_bot(bot)
return result
@staticmethod
def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
"""Should be called by subclasses that override de_json to ensure that the input
is not altered. Whoever calls de_json might still want to use the original input
for something else.
"""
return None if data is None else data.copy()
@classmethod
def _de_json(
cls: type[Tele_co],
data: Optional[JSONDict],
bot: Optional["Bot"],
api_kwargs: Optional[JSONDict] = None,
) -> Optional[Tele_co]:
if data is None:
return None
# try-except is significantly faster in case we already have a correct argument set
try:
obj = cls(**data, api_kwargs=api_kwargs)
except TypeError as exc:
if "__init__() got an unexpected keyword argument" not in str(exc):
raise
if cls.__INIT_PARAMS_CHECK is not cls:
signature = inspect.signature(cls)
cls.__INIT_PARAMS = set(signature.parameters.keys())
cls.__INIT_PARAMS_CHECK = cls
api_kwargs = api_kwargs or {}
existing_kwargs: JSONDict = {}
for key, value in data.items():
(existing_kwargs if key in cls.__INIT_PARAMS else api_kwargs)[key] = value
obj = cls(api_kwargs=api_kwargs, **existing_kwargs)
obj.set_bot(bot=bot)
return obj
@classmethod
def de_json(
cls: type[Tele_co], data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional[Tele_co]:
"""Converts JSON data to a Telegram object.
Args:
data (dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`, optional): The bot associated with this object. Defaults to
:obj:`None`, in which case shortcut methods will not be available.
.. versionchanged:: 21.4
:paramref:`bot` is now optional and defaults to :obj:`None`
Returns:
The Telegram object.
"""
return cls._de_json(data=data, bot=bot)
@classmethod
def de_list(
cls: type[Tele_co], data: Optional[list[JSONDict]], bot: Optional["Bot"] = None
) -> tuple[Tele_co, ...]:
"""Converts a list of JSON objects to a tuple of Telegram objects.
.. 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`, optional): The bot associated with these object. Defaults
to :obj:`None`, in which case shortcut methods will not be available.
.. versionchanged:: 21.4
:paramref:`bot` is now optional and defaults to :obj:`None`
Returns:
A tuple of Telegram objects.
"""
if not data:
return ()
return tuple(obj for obj in (cls.de_json(d, bot) for d in data) if obj is not None)
@contextmanager
def _unfrozen(self: Tele_co) -> Iterator[Tele_co]:
"""Context manager to temporarily unfreeze the object. For internal use only.
Note:
with to._unfrozen() as other_to:
assert to is other_to
"""
self._unfreeze()
yield self
self._freeze()
def _freeze(self) -> None:
self._frozen = True
def _unfreeze(self) -> None:
self._frozen = False
def _apply_api_kwargs(self, api_kwargs: JSONDict) -> None:
"""Loops through the api kwargs and for every key that exists as attribute of the
object (and is None), it moves the value from `api_kwargs` to the attribute.
*Edits `api_kwargs` in place!*
This method is currently only called in the unpickling process, i.e. not on "normal" init.
This is because
* automating this is tricky to get right: It should be called at the *end* of the __init__,
preferably only once at the end of the __init__ of the last child class. This could be
done via __init_subclass__, but it's hard to not destroy the signature of __init__ in the
process.
* calling it manually in every __init__ is tedious
* There probably is no use case for it anyway. If you manually initialize a TO subclass,
then you can pass everything as proper argument.
"""
# we convert to list to ensure that the list doesn't change length while we loop
for key in list(api_kwargs.keys()):
# property attributes are not settable, so we need to set the private attribute
if isinstance(getattr(self.__class__, key, None), property):
# if setattr fails, we'll just leave the value in api_kwargs:
with contextlib.suppress(AttributeError):
setattr(self, f"_{key}", api_kwargs.pop(key))
elif getattr(self, key, True) is None:
setattr(self, key, api_kwargs.pop(key))
def _get_attrs_names(self, include_private: bool) -> Iterator[str]:
"""
Returns the names of the attributes of this object. This is used to determine which
attributes should be serialized when pickling the object.
Args:
include_private (:obj:`bool`): Whether to include private attributes.
Returns:
Iterator[:obj:`str`]: An iterator over the names of the attributes of this object.
"""
# We want to get all attributes for the class, using self.__slots__ only includes the
# attributes used by that class itself, and not its superclass(es). Hence, we get its MRO
# and then get their attributes. The `[:-1]` slice excludes the `object` class
all_slots = (s for c in self.__class__.__mro__[:-1] for s in c.__slots__) # type: ignore
# chain the class's slots with the user defined subclass __dict__ (class has no slots)
all_attrs = (
chain(all_slots, self.__dict__.keys()) if hasattr(self, "__dict__") else all_slots
)
if include_private:
return all_attrs
return (attr for attr in all_attrs if not attr.startswith("_"))
def _get_attrs(
self,
include_private: bool = False,
recursive: bool = False,
remove_bot: bool = False,
convert_default_vault: bool = True,
) -> dict[str, Union[str, object]]:
"""This method is used for obtaining the attributes of the object.
Args:
include_private (:obj:`bool`): Whether the result should include private variables.
recursive (:obj:`bool`): If :obj:`True`, will convert any ``TelegramObjects`` (if
found) in the attributes to a dictionary. Else, preserves it as an object itself.
remove_bot (:obj:`bool`): Whether the bot should be included in the result.
convert_default_vault (:obj:`bool`): Whether :class:`telegram.DefaultValue` should be
converted to its true value. This is necessary when converting to a dictionary for
end users since DefaultValue is used in some classes that work with
`tg.ext.defaults` (like `LinkPreviewOptions`)
Returns:
:obj:`dict`: A dict where the keys are attribute names and values are their values.
"""
data = {}
for key in self._get_attrs_names(include_private=include_private):
value = (
DefaultValue.get_value(getattr(self, key, None))
if convert_default_vault
else getattr(self, key, None)
)
if value is not None:
if recursive and hasattr(value, "to_dict"):
data[key] = value.to_dict(recursive=True)
else:
data[key] = value
elif not recursive:
data[key] = value
if recursive and data.get("from_user"):
data["from"] = data.pop("from_user", None)
if remove_bot:
data.pop("_bot", None)
return data
def to_json(self) -> str:
"""Gives a JSON representation of object.
.. versionchanged:: 20.0
Now includes all entries of :attr:`api_kwargs`.
Returns:
:obj:`str`
"""
return json.dumps(self.to_dict())
def to_dict(self, recursive: bool = True) -> JSONDict:
"""Gives representation of object as :obj:`dict`.
.. versionchanged:: 20.0
* Now includes all entries of :attr:`api_kwargs`.
* Attributes whose values are empty sequences are no longer included.
Args:
recursive (:obj:`bool`, optional): If :obj:`True`, will convert any TelegramObjects
(if found) in the attributes to a dictionary. Else, preserves it as an object
itself. Defaults to :obj:`True`.
.. versionadded:: 20.0
Returns:
:obj:`dict`
"""
out = self._get_attrs(recursive=recursive)
# Now we should convert TGObjects to dicts inside objects such as sequences, and convert
# datetimes to timestamps. This mostly eliminates the need for subclasses to override
# `to_dict`
pop_keys: set[str] = set()
for key, value in out.items():
if isinstance(value, (tuple, list)):
if not value:
# not popping directly to avoid changing the dict size during iteration
pop_keys.add(key)
continue
val = [] # empty list to append our converted values to
for item in value:
if hasattr(item, "to_dict"):
val.append(item.to_dict(recursive=recursive))
# This branch is useful for e.g. tuple[tuple[PhotoSize|KeyboardButton]]
elif isinstance(item, (tuple, list)):
val.append(
[
i.to_dict(recursive=recursive) if hasattr(i, "to_dict") else i
for i in item
]
)
else: # if it's not a TGObject, just append it. E.g. [TGObject, 2]
val.append(item)
out[key] = val
elif isinstance(value, dtm.datetime):
out[key] = to_timestamp(value)
elif isinstance(value, dtm.timedelta):
out[key] = value.total_seconds()
for key in pop_keys:
out.pop(key)
# Effectively "unpack" api_kwargs into `out`:
out.update(out.pop("api_kwargs", {})) # type: ignore[call-overload]
return out
def get_bot(self) -> "Bot":
"""Returns the :class:`telegram.Bot` instance associated with this object.
.. seealso:: :meth:`set_bot`
.. versionadded: 20.0
Raises:
RuntimeError: If no :class:`telegram.Bot` instance was set for this object.
"""
if self._bot is None:
raise RuntimeError(
"This object has no bot associated with it. Shortcuts cannot be used."
)
return self._bot
def set_bot(self, bot: Optional["Bot"]) -> None:
"""Sets the :class:`telegram.Bot` instance associated with this object.
.. seealso:: :meth:`get_bot`
.. versionadded: 20.0
Arguments:
bot (:class:`telegram.Bot` | :obj:`None`): The bot instance.
"""
self._bot = bot
@@ -1,803 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 Update."""
from typing import TYPE_CHECKING, Final, Optional, Union
from telegram import constants
from telegram._business import BusinessConnection, BusinessMessagesDeleted
from telegram._callbackquery import CallbackQuery
from telegram._chatboost import ChatBoostRemoved, ChatBoostUpdated
from telegram._chatjoinrequest import ChatJoinRequest
from telegram._chatmemberupdated import ChatMemberUpdated
from telegram._choseninlineresult import ChosenInlineResult
from telegram._inline.inlinequery import InlineQuery
from telegram._message import Message
from telegram._messagereactionupdated import MessageReactionCountUpdated, MessageReactionUpdated
from telegram._paidmedia import PaidMediaPurchased
from telegram._payment.precheckoutquery import PreCheckoutQuery
from telegram._payment.shippingquery import ShippingQuery
from telegram._poll import Poll, PollAnswer
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
if TYPE_CHECKING:
from telegram import Bot, Chat, User
class Update(TelegramObject):
"""This object represents an incoming update.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`update_id` is equal.
Note:
At most one of the optional parameters can be present in any given update.
.. seealso:: :wiki:`Your First Bot <Extensions---Your-first-Bot>`
Args:
update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
certain positive number and increase sequentially. This ID becomes especially handy if
you're using Webhooks, since it allows you to ignore repeated updates or to restore the
correct update sequence, should they get out of order. If there are no new updates for
at least a week, then identifier of the next update will be chosen randomly instead of
sequentially.
message (:class:`telegram.Message`, optional): New incoming message of any kind - text,
photo, sticker, etc.
edited_message (:class:`telegram.Message`, optional): New version of a message that is
known to the bot and was edited. This update may at times be triggered by changes to
message fields that are either unavailable or not actively used by your bot.
channel_post (:class:`telegram.Message`, optional): New incoming channel post of any kind
- text, photo, sticker, etc.
edited_channel_post (:class:`telegram.Message`, optional): New version of a channel post
that is known to the bot and was edited. This update may at times be triggered by
changes to message fields that are either unavailable or not actively used by your bot.
inline_query (:class:`telegram.InlineQuery`, optional): New incoming inline query.
chosen_inline_result (:class:`telegram.ChosenInlineResult`, optional): The result of an
inline query that was chosen by a user and sent to their chat partner.
callback_query (:class:`telegram.CallbackQuery`, optional): New incoming callback query.
shipping_query (:class:`telegram.ShippingQuery`, optional): New incoming shipping query.
Only for invoices with flexible price.
pre_checkout_query (:class:`telegram.PreCheckoutQuery`, optional): New incoming
pre-checkout query. Contains full information about checkout.
poll (:class:`telegram.Poll`, optional): New poll state. Bots receive only updates about
manually stopped polls and polls, which are sent by the bot.
poll_answer (:class:`telegram.PollAnswer`, optional): A user changed their answer
in a non-anonymous poll. Bots receive new votes only in polls that were sent
by the bot itself.
my_chat_member (:class:`telegram.ChatMemberUpdated`, optional): The bot's chat member
status was updated in a chat. For private chats, this update is received only when the
bot is blocked or unblocked by the user.
.. versionadded:: 13.4
chat_member (:class:`telegram.ChatMemberUpdated`, optional): A chat member's status was
updated in a chat. The bot must be an administrator in the chat and must explicitly
specify :attr:`CHAT_MEMBER` in the list of
:paramref:`telegram.ext.Application.run_polling.allowed_updates` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Application.run_polling` and
:meth:`telegram.ext.Application.run_webhook`).
.. versionadded:: 13.4
chat_join_request (:class:`telegram.ChatJoinRequest`, optional): A request to join the
chat has been sent. The bot must have the
:attr:`telegram.ChatPermissions.can_invite_users` administrator right in the chat to
receive these updates.
.. versionadded:: 13.8
chat_boost (:class:`telegram.ChatBoostUpdated`, optional): A chat boost was added or
changed. The bot must be an administrator in the chat to receive these updates.
.. versionadded:: 20.8
removed_chat_boost (:class:`telegram.ChatBoostRemoved`, optional): A boost was removed from
a chat. The bot must be an administrator in the chat to receive these updates.
.. versionadded:: 20.8
message_reaction (:class:`telegram.MessageReactionUpdated`, optional): A reaction to a
message was changed by a user. The bot must be an administrator in the chat and must
explicitly specify :attr:`MESSAGE_REACTION` in the list of
:paramref:`telegram.ext.Application.run_polling.allowed_updates` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Application.run_polling` and
:meth:`telegram.ext.Application.run_webhook`). The update isn't received for reactions
set by bots.
.. versionadded:: 20.8
message_reaction_count (:class:`telegram.MessageReactionCountUpdated`, optional): Reactions
to a message with anonymous reactions were changed. The bot must be an administrator in
the chat and must explicitly specify :attr:`MESSAGE_REACTION_COUNT` in the list of
:paramref:`telegram.ext.Application.run_polling.allowed_updates` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Application.run_polling` and
:meth:`telegram.ext.Application.run_webhook`). The updates are grouped and can be sent
with delay up to a few minutes.
.. versionadded:: 20.8
business_connection (:class:`telegram.BusinessConnection`, optional): The bot was connected
to or disconnected from a business account, or a user edited an existing connection
with the bot.
.. versionadded:: 21.1
business_message (:class:`telegram.Message`, optional): New message from a connected
business account.
.. versionadded:: 21.1
edited_business_message (:class:`telegram.Message`, optional): New version of a message
from a connected business account.
.. versionadded:: 21.1
deleted_business_messages (:class:`telegram.BusinessMessagesDeleted`, optional): Messages
were deleted from a connected business account.
.. versionadded:: 21.1
purchased_paid_media (:class:`telegram.PaidMediaPurchased`, optional): A user purchased
paid media with a non-empty payload sent by the bot in a non-channel chat.
.. versionadded:: 21.6
Attributes:
update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
certain positive number and increase sequentially. This ID becomes especially handy if
you're using Webhooks, since it allows you to ignore repeated updates or to restore the
correct update sequence, should they get out of order. If there are no new updates for
at least a week, then identifier of the next update will be chosen randomly instead of
sequentially.
message (:class:`telegram.Message`): Optional. New incoming message of any kind - text,
photo, sticker, etc.
edited_message (:class:`telegram.Message`): Optional. New version of a message that is
known to the bot and was edited. This update may at times be triggered by changes to
message fields that are either unavailable or not actively used by your bot.
channel_post (:class:`telegram.Message`): Optional. New incoming channel post of any kind
- text, photo, sticker, etc.
edited_channel_post (:class:`telegram.Message`): Optional. New version of a channel post
that is known to the bot and was edited. This update may at times be triggered by
changes to message fields that are either unavailable or not actively used by your bot.
inline_query (:class:`telegram.InlineQuery`): Optional. New incoming inline query.
chosen_inline_result (:class:`telegram.ChosenInlineResult`): Optional. The result of an
inline query that was chosen by a user and sent to their chat partner.
callback_query (:class:`telegram.CallbackQuery`): Optional. New incoming callback query.
Examples:
:any:`Arbitrary Callback Data Bot <examples.arbitrarycallbackdatabot>`
shipping_query (:class:`telegram.ShippingQuery`): Optional. New incoming shipping query.
Only for invoices with flexible price.
pre_checkout_query (:class:`telegram.PreCheckoutQuery`): Optional. New incoming
pre-checkout query. Contains full information about checkout.
poll (:class:`telegram.Poll`): Optional. New poll state. Bots receive only updates about
manually stopped polls and polls, which are sent by the bot.
poll_answer (:class:`telegram.PollAnswer`): Optional. A user changed their answer
in a non-anonymous poll. Bots receive new votes only in polls that were sent
by the bot itself.
my_chat_member (:class:`telegram.ChatMemberUpdated`): Optional. The bot's chat member
status was updated in a chat. For private chats, this update is received only when the
bot is blocked or unblocked by the user.
.. versionadded:: 13.4
chat_member (:class:`telegram.ChatMemberUpdated`): Optional. A chat member's status was
updated in a chat. The bot must be an administrator in the chat and must explicitly
specify :attr:`CHAT_MEMBER` in the list of
:paramref:`telegram.ext.Application.run_polling.allowed_updates` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Application.run_polling` and
:meth:`telegram.ext.Application.run_webhook`).
.. versionadded:: 13.4
chat_join_request (:class:`telegram.ChatJoinRequest`): Optional. A request to join the
chat has been sent. The bot must have the
:attr:`telegram.ChatPermissions.can_invite_users` administrator right in the chat to
receive these updates.
.. versionadded:: 13.8
chat_boost (:class:`telegram.ChatBoostUpdated`): Optional. A chat boost was added or
changed. The bot must be an administrator in the chat to receive these updates.
.. versionadded:: 20.8
removed_chat_boost (:class:`telegram.ChatBoostRemoved`): Optional. A boost was removed from
a chat. The bot must be an administrator in the chat to receive these updates.
.. versionadded:: 20.8
message_reaction (:class:`telegram.MessageReactionUpdated`): Optional. A reaction to a
message was changed by a user. The bot must be an administrator in the chat and must
explicitly specify :attr:`MESSAGE_REACTION` in the list of
:paramref:`telegram.ext.Application.run_polling.allowed_updates` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Application.run_polling` and
:meth:`telegram.ext.Application.run_webhook`). The update isn't received for reactions
set by bots.
.. versionadded:: 20.8
message_reaction_count (:class:`telegram.MessageReactionCountUpdated`): Optional. Reactions
to a message with anonymous reactions were changed. The bot must be an administrator in
the chat and must explicitly specify :attr:`MESSAGE_REACTION_COUNT` in the list of
:paramref:`telegram.ext.Application.run_polling.allowed_updates` to receive these
updates (see :meth:`telegram.Bot.get_updates`, :meth:`telegram.Bot.set_webhook`,
:meth:`telegram.ext.Application.run_polling` and
:meth:`telegram.ext.Application.run_webhook`). The updates are grouped and can be sent
with delay up to a few minutes.
.. versionadded:: 20.8
business_connection (:class:`telegram.BusinessConnection`): Optional. The bot was connected
to or disconnected from a business account, or a user edited an existing connection
with the bot.
.. versionadded:: 21.1
business_message (:class:`telegram.Message`): Optional. New message from a connected
business account.
.. versionadded:: 21.1
edited_business_message (:class:`telegram.Message`): Optional. New version of a message
from a connected business account.
.. versionadded:: 21.1
deleted_business_messages (:class:`telegram.BusinessMessagesDeleted`): Optional. Messages
were deleted from a connected business account.
.. versionadded:: 21.1
purchased_paid_media (:class:`telegram.PaidMediaPurchased`): Optional. A user purchased
paid media with a non-empty payload sent by the bot in a non-channel chat.
.. versionadded:: 21.6
"""
__slots__ = (
"_effective_chat",
"_effective_message",
"_effective_sender",
"_effective_user",
"business_connection",
"business_message",
"callback_query",
"channel_post",
"chat_boost",
"chat_join_request",
"chat_member",
"chosen_inline_result",
"deleted_business_messages",
"edited_business_message",
"edited_channel_post",
"edited_message",
"inline_query",
"message",
"message_reaction",
"message_reaction_count",
"my_chat_member",
"poll",
"poll_answer",
"pre_checkout_query",
"purchased_paid_media",
"removed_chat_boost",
"shipping_query",
"update_id",
)
MESSAGE: Final[str] = constants.UpdateType.MESSAGE
""":const:`telegram.constants.UpdateType.MESSAGE`
.. versionadded:: 13.5"""
EDITED_MESSAGE: Final[str] = constants.UpdateType.EDITED_MESSAGE
""":const:`telegram.constants.UpdateType.EDITED_MESSAGE`
.. versionadded:: 13.5"""
CHANNEL_POST: Final[str] = constants.UpdateType.CHANNEL_POST
""":const:`telegram.constants.UpdateType.CHANNEL_POST`
.. versionadded:: 13.5"""
EDITED_CHANNEL_POST: Final[str] = constants.UpdateType.EDITED_CHANNEL_POST
""":const:`telegram.constants.UpdateType.EDITED_CHANNEL_POST`
.. versionadded:: 13.5"""
INLINE_QUERY: Final[str] = constants.UpdateType.INLINE_QUERY
""":const:`telegram.constants.UpdateType.INLINE_QUERY`
.. versionadded:: 13.5"""
CHOSEN_INLINE_RESULT: Final[str] = constants.UpdateType.CHOSEN_INLINE_RESULT
""":const:`telegram.constants.UpdateType.CHOSEN_INLINE_RESULT`
.. versionadded:: 13.5"""
CALLBACK_QUERY: Final[str] = constants.UpdateType.CALLBACK_QUERY
""":const:`telegram.constants.UpdateType.CALLBACK_QUERY`
.. versionadded:: 13.5"""
SHIPPING_QUERY: Final[str] = constants.UpdateType.SHIPPING_QUERY
""":const:`telegram.constants.UpdateType.SHIPPING_QUERY`
.. versionadded:: 13.5"""
PRE_CHECKOUT_QUERY: Final[str] = constants.UpdateType.PRE_CHECKOUT_QUERY
""":const:`telegram.constants.UpdateType.PRE_CHECKOUT_QUERY`
.. versionadded:: 13.5"""
POLL: Final[str] = constants.UpdateType.POLL
""":const:`telegram.constants.UpdateType.POLL`
.. versionadded:: 13.5"""
POLL_ANSWER: Final[str] = constants.UpdateType.POLL_ANSWER
""":const:`telegram.constants.UpdateType.POLL_ANSWER`
.. versionadded:: 13.5"""
MY_CHAT_MEMBER: Final[str] = constants.UpdateType.MY_CHAT_MEMBER
""":const:`telegram.constants.UpdateType.MY_CHAT_MEMBER`
.. versionadded:: 13.5"""
CHAT_MEMBER: Final[str] = constants.UpdateType.CHAT_MEMBER
""":const:`telegram.constants.UpdateType.CHAT_MEMBER`
.. versionadded:: 13.5"""
CHAT_JOIN_REQUEST: Final[str] = constants.UpdateType.CHAT_JOIN_REQUEST
""":const:`telegram.constants.UpdateType.CHAT_JOIN_REQUEST`
.. versionadded:: 13.8"""
CHAT_BOOST: Final[str] = constants.UpdateType.CHAT_BOOST
""":const:`telegram.constants.UpdateType.CHAT_BOOST`
.. versionadded:: 20.8"""
REMOVED_CHAT_BOOST: Final[str] = constants.UpdateType.REMOVED_CHAT_BOOST
""":const:`telegram.constants.UpdateType.REMOVED_CHAT_BOOST`
.. versionadded:: 20.8"""
MESSAGE_REACTION: Final[str] = constants.UpdateType.MESSAGE_REACTION
""":const:`telegram.constants.UpdateType.MESSAGE_REACTION`
.. versionadded:: 20.8"""
MESSAGE_REACTION_COUNT: Final[str] = constants.UpdateType.MESSAGE_REACTION_COUNT
""":const:`telegram.constants.UpdateType.MESSAGE_REACTION_COUNT`
.. versionadded:: 20.8"""
BUSINESS_CONNECTION: Final[str] = constants.UpdateType.BUSINESS_CONNECTION
""":const:`telegram.constants.UpdateType.BUSINESS_CONNECTION`
.. versionadded:: 21.1"""
BUSINESS_MESSAGE: Final[str] = constants.UpdateType.BUSINESS_MESSAGE
""":const:`telegram.constants.UpdateType.BUSINESS_MESSAGE`
.. versionadded:: 21.1"""
EDITED_BUSINESS_MESSAGE: Final[str] = constants.UpdateType.EDITED_BUSINESS_MESSAGE
""":const:`telegram.constants.UpdateType.EDITED_BUSINESS_MESSAGE`
.. versionadded:: 21.1"""
DELETED_BUSINESS_MESSAGES: Final[str] = constants.UpdateType.DELETED_BUSINESS_MESSAGES
""":const:`telegram.constants.UpdateType.DELETED_BUSINESS_MESSAGES`
.. versionadded:: 21.1"""
PURCHASED_PAID_MEDIA: Final[str] = constants.UpdateType.PURCHASED_PAID_MEDIA
""":const:`telegram.constants.UpdateType.PURCHASED_PAID_MEDIA`
.. versionadded:: 21.6
"""
ALL_TYPES: Final[list[str]] = list(constants.UpdateType)
"""list[:obj:`str`]: A list of all available update types.
.. versionadded:: 13.5"""
def __init__(
self,
update_id: int,
message: Optional[Message] = None,
edited_message: Optional[Message] = None,
channel_post: Optional[Message] = None,
edited_channel_post: Optional[Message] = None,
inline_query: Optional[InlineQuery] = None,
chosen_inline_result: Optional[ChosenInlineResult] = None,
callback_query: Optional[CallbackQuery] = None,
shipping_query: Optional[ShippingQuery] = None,
pre_checkout_query: Optional[PreCheckoutQuery] = None,
poll: Optional[Poll] = None,
poll_answer: Optional[PollAnswer] = None,
my_chat_member: Optional[ChatMemberUpdated] = None,
chat_member: Optional[ChatMemberUpdated] = None,
chat_join_request: Optional[ChatJoinRequest] = None,
chat_boost: Optional[ChatBoostUpdated] = None,
removed_chat_boost: Optional[ChatBoostRemoved] = None,
message_reaction: Optional[MessageReactionUpdated] = None,
message_reaction_count: Optional[MessageReactionCountUpdated] = None,
business_connection: Optional[BusinessConnection] = None,
business_message: Optional[Message] = None,
edited_business_message: Optional[Message] = None,
deleted_business_messages: Optional[BusinessMessagesDeleted] = None,
purchased_paid_media: Optional[PaidMediaPurchased] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.update_id: int = update_id
# Optionals
self.message: Optional[Message] = message
self.edited_message: Optional[Message] = edited_message
self.inline_query: Optional[InlineQuery] = inline_query
self.chosen_inline_result: Optional[ChosenInlineResult] = chosen_inline_result
self.callback_query: Optional[CallbackQuery] = callback_query
self.shipping_query: Optional[ShippingQuery] = shipping_query
self.pre_checkout_query: Optional[PreCheckoutQuery] = pre_checkout_query
self.channel_post: Optional[Message] = channel_post
self.edited_channel_post: Optional[Message] = edited_channel_post
self.poll: Optional[Poll] = poll
self.poll_answer: Optional[PollAnswer] = poll_answer
self.my_chat_member: Optional[ChatMemberUpdated] = my_chat_member
self.chat_member: Optional[ChatMemberUpdated] = chat_member
self.chat_join_request: Optional[ChatJoinRequest] = chat_join_request
self.chat_boost: Optional[ChatBoostUpdated] = chat_boost
self.removed_chat_boost: Optional[ChatBoostRemoved] = removed_chat_boost
self.message_reaction: Optional[MessageReactionUpdated] = message_reaction
self.message_reaction_count: Optional[MessageReactionCountUpdated] = message_reaction_count
self.business_connection: Optional[BusinessConnection] = business_connection
self.business_message: Optional[Message] = business_message
self.edited_business_message: Optional[Message] = edited_business_message
self.deleted_business_messages: Optional[BusinessMessagesDeleted] = (
deleted_business_messages
)
self.purchased_paid_media: Optional[PaidMediaPurchased] = purchased_paid_media
self._effective_user: Optional[User] = None
self._effective_sender: Optional[Union[User, Chat]] = None
self._effective_chat: Optional[Chat] = None
self._effective_message: Optional[Message] = None
self._id_attrs = (self.update_id,)
self._freeze()
@property
def effective_user(self) -> Optional["User"]:
"""
:class:`telegram.User`: The user that sent this update, no matter what kind of update this
is. If no user is associated with this update, this gives :obj:`None`. This is the case
if any of
* :attr:`channel_post`
* :attr:`edited_channel_post`
* :attr:`poll`
* :attr:`chat_boost`
* :attr:`removed_chat_boost`
* :attr:`message_reaction_count`
* :attr:`deleted_business_messages`
is present.
.. versionchanged:: 21.1
This property now also considers :attr:`business_connection`, :attr:`business_message`
and :attr:`edited_business_message`.
.. versionchanged:: 21.6
This property now also considers :attr:`purchased_paid_media`.
Example:
* If :attr:`message` is present, this will give
:attr:`telegram.Message.from_user`.
* If :attr:`poll_answer` is present, this will give :attr:`telegram.PollAnswer.user`.
"""
if self._effective_user:
return self._effective_user
user = None
if self.message:
user = self.message.from_user
elif self.edited_message:
user = self.edited_message.from_user
elif self.inline_query:
user = self.inline_query.from_user
elif self.chosen_inline_result:
user = self.chosen_inline_result.from_user
elif self.callback_query:
user = self.callback_query.from_user
elif self.shipping_query:
user = self.shipping_query.from_user
elif self.pre_checkout_query:
user = self.pre_checkout_query.from_user
elif self.poll_answer:
user = self.poll_answer.user
elif self.my_chat_member:
user = self.my_chat_member.from_user
elif self.chat_member:
user = self.chat_member.from_user
elif self.chat_join_request:
user = self.chat_join_request.from_user
elif self.message_reaction:
user = self.message_reaction.user
elif self.business_message:
user = self.business_message.from_user
elif self.edited_business_message:
user = self.edited_business_message.from_user
elif self.business_connection:
user = self.business_connection.user
elif self.purchased_paid_media:
user = self.purchased_paid_media.from_user
self._effective_user = user
return user
@property
def effective_sender(self) -> Optional[Union["User", "Chat"]]:
"""
:class:`telegram.User` or :class:`telegram.Chat`: The user or chat that sent this update,
no matter what kind of update this is.
Note:
* Depending on the type of update and the user's 'Remain anonymous' setting, this
could either be :class:`telegram.User`, :class:`telegram.Chat` or :obj:`None`.
If no user whatsoever is associated with this update, this gives :obj:`None`. This
is the case if any of
* :attr:`poll`
* :attr:`chat_boost`
* :attr:`removed_chat_boost`
* :attr:`message_reaction_count`
* :attr:`deleted_business_messages`
is present.
Example:
* If :attr:`message` is present, this will give either
:attr:`telegram.Message.from_user` or :attr:`telegram.Message.sender_chat`.
* If :attr:`poll_answer` is present, this will give either
:attr:`telegram.PollAnswer.user` or :attr:`telegram.PollAnswer.voter_chat`.
* If :attr:`channel_post` is present, this will give
:attr:`telegram.Message.sender_chat`.
.. versionadded:: 21.1
"""
if self._effective_sender:
return self._effective_sender
sender: Optional[Union[User, Chat]] = None
if message := (
self.message
or self.edited_message
or self.channel_post
or self.edited_channel_post
or self.business_message
or self.edited_business_message
):
sender = message.sender_chat
elif self.poll_answer:
sender = self.poll_answer.voter_chat
elif self.message_reaction:
sender = self.message_reaction.actor_chat
if sender is None:
sender = self.effective_user
self._effective_sender = sender
return sender
@property
def effective_chat(self) -> Optional["Chat"]:
"""
:class:`telegram.Chat`: The chat that this update was sent in, no matter what kind of
update this is.
If no chat is associated with this update, this gives :obj:`None`.
This is the case, if :attr:`inline_query`,
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
:attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll`,
:attr:`poll_answer`, :attr:`business_connection`, or :attr:`purchased_paid_media`
is present.
.. versionchanged:: 21.1
This property now also considers :attr:`business_message`,
:attr:`edited_business_message`, and :attr:`deleted_business_messages`.
Example:
If :attr:`message` is present, this will give :attr:`telegram.Message.chat`.
"""
if self._effective_chat:
return self._effective_chat
chat = None
if self.message:
chat = self.message.chat
elif self.edited_message:
chat = self.edited_message.chat
elif self.callback_query and self.callback_query.message:
chat = self.callback_query.message.chat
elif self.channel_post:
chat = self.channel_post.chat
elif self.edited_channel_post:
chat = self.edited_channel_post.chat
elif self.my_chat_member:
chat = self.my_chat_member.chat
elif self.chat_member:
chat = self.chat_member.chat
elif self.chat_join_request:
chat = self.chat_join_request.chat
elif self.chat_boost:
chat = self.chat_boost.chat
elif self.removed_chat_boost:
chat = self.removed_chat_boost.chat
elif self.message_reaction:
chat = self.message_reaction.chat
elif self.message_reaction_count:
chat = self.message_reaction_count.chat
elif self.business_message:
chat = self.business_message.chat
elif self.edited_business_message:
chat = self.edited_business_message.chat
elif self.deleted_business_messages:
chat = self.deleted_business_messages.chat
self._effective_chat = chat
return chat
@property
def effective_message(self) -> Optional[Message]:
"""
:class:`telegram.Message`: The message included in this update, no matter what kind of
update this is. More precisely, this will be the message contained in :attr:`message`,
:attr:`edited_message`, :attr:`channel_post`, :attr:`edited_channel_post` or
:attr:`callback_query` (i.e. :attr:`telegram.CallbackQuery.message`) or :obj:`None`, if
none of those are present.
.. versionchanged:: 21.1
This property now also considers :attr:`business_message`, and
:attr:`edited_business_message`.
Tip:
This property will only ever return objects of type :class:`telegram.Message` or
:obj:`None`, never :class:`telegram.MaybeInaccessibleMessage` or
:class:`telegram.InaccessibleMessage`.
Currently, this is only relevant for :attr:`callback_query`, as
:attr:`telegram.CallbackQuery.message` is the only attribute considered by this
property that can be an object of these types.
"""
if self._effective_message:
return self._effective_message
message: Optional[Message] = None
if self.message:
message = self.message
elif self.edited_message:
message = self.edited_message
elif self.callback_query:
if (
isinstance(cbq_message := self.callback_query.message, Message)
or cbq_message is None
):
message = cbq_message
else:
warn(
(
"`update.callback_query` is not `None`, but of type "
f"`{cbq_message.__class__.__name__}`. This is not considered by "
"`Update.effective_message`. Please manually access this attribute "
"if necessary."
),
stacklevel=2,
)
elif self.channel_post:
message = self.channel_post
elif self.edited_channel_post:
message = self.edited_channel_post
elif self.business_message:
message = self.business_message
elif self.edited_business_message:
message = self.edited_business_message
self._effective_message = message
return message
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Update"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["message"] = Message.de_json(data.get("message"), bot)
data["edited_message"] = Message.de_json(data.get("edited_message"), bot)
data["inline_query"] = InlineQuery.de_json(data.get("inline_query"), bot)
data["chosen_inline_result"] = ChosenInlineResult.de_json(
data.get("chosen_inline_result"), bot
)
data["callback_query"] = CallbackQuery.de_json(data.get("callback_query"), bot)
data["shipping_query"] = ShippingQuery.de_json(data.get("shipping_query"), bot)
data["pre_checkout_query"] = PreCheckoutQuery.de_json(data.get("pre_checkout_query"), bot)
data["channel_post"] = Message.de_json(data.get("channel_post"), bot)
data["edited_channel_post"] = Message.de_json(data.get("edited_channel_post"), bot)
data["poll"] = Poll.de_json(data.get("poll"), bot)
data["poll_answer"] = PollAnswer.de_json(data.get("poll_answer"), bot)
data["my_chat_member"] = ChatMemberUpdated.de_json(data.get("my_chat_member"), bot)
data["chat_member"] = ChatMemberUpdated.de_json(data.get("chat_member"), bot)
data["chat_join_request"] = ChatJoinRequest.de_json(data.get("chat_join_request"), bot)
data["chat_boost"] = ChatBoostUpdated.de_json(data.get("chat_boost"), bot)
data["removed_chat_boost"] = ChatBoostRemoved.de_json(data.get("removed_chat_boost"), bot)
data["message_reaction"] = MessageReactionUpdated.de_json(
data.get("message_reaction"), bot
)
data["message_reaction_count"] = MessageReactionCountUpdated.de_json(
data.get("message_reaction_count"), bot
)
data["business_connection"] = BusinessConnection.de_json(
data.get("business_connection"), bot
)
data["business_message"] = Message.de_json(data.get("business_message"), bot)
data["edited_business_message"] = Message.de_json(data.get("edited_business_message"), bot)
data["deleted_business_messages"] = BusinessMessagesDeleted.de_json(
data.get("deleted_business_messages"), bot
)
data["purchased_paid_media"] = PaidMediaPurchased.de_json(
data.get("purchased_paid_media"), bot
)
return super().de_json(data=data, bot=bot)
File diff suppressed because it is too large Load Diff
@@ -1,85 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 UserProfilePhotos."""
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class UserProfilePhotos(TelegramObject):
"""This object represents a user's profile pictures.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`total_count` and :attr:`photos` are equal.
Args:
total_count (:obj:`int`): Total number of profile pictures the target user has.
photos (Sequence[Sequence[:class:`telegram.PhotoSize`]]): Requested profile pictures (in up
to 4 sizes each).
.. versionchanged:: 20.0
|sequenceclassargs|
Attributes:
total_count (:obj:`int`): Total number of profile pictures.
photos (tuple[tuple[:class:`telegram.PhotoSize`]]): Requested profile pictures (in up to 4
sizes each).
.. versionchanged:: 20.0
|tupleclassattrs|
"""
__slots__ = ("photos", "total_count")
def __init__(
self,
total_count: int,
photos: Sequence[Sequence[PhotoSize]],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.total_count: int = total_count
self.photos: tuple[tuple[PhotoSize, ...], ...] = tuple(tuple(sizes) for sizes in photos)
self._id_attrs = (self.total_count, self.photos)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["UserProfilePhotos"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photos"] = [PhotoSize.de_list(photo, bot) for photo in data["photos"]]
return super().de_json(data=data, bot=bot)
@@ -1,62 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 helper functions related to parsing arguments for classes and methods.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from collections.abc import Sequence
from typing import Optional, TypeVar
from telegram._linkpreviewoptions import LinkPreviewOptions
from telegram._utils.types import ODVInput
T = TypeVar("T")
def parse_sequence_arg(arg: Optional[Sequence[T]]) -> tuple[T, ...]:
"""Parses an optional sequence into a tuple
Args:
arg (:obj:`Sequence`): The sequence to parse.
Returns:
:obj:`Tuple`: The sequence converted to a tuple or an empty tuple.
"""
return tuple(arg) if arg else ()
def parse_lpo_and_dwpp(
disable_web_page_preview: Optional[bool], link_preview_options: ODVInput[LinkPreviewOptions]
) -> ODVInput[LinkPreviewOptions]:
"""Wrapper around warn_about_deprecated_arg_return_new_arg. Takes care of converting
disable_web_page_preview to LinkPreviewOptions.
"""
if disable_web_page_preview and link_preview_options:
raise ValueError(
"Parameters `disable_web_page_preview` and `link_preview_options` are mutually "
"exclusive."
)
if disable_web_page_preview is not None:
link_preview_options = LinkPreviewOptions(is_disabled=disable_web_page_preview)
return link_preview_options
@@ -1,226 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 helper functions related to datetime and timestamp conversations.
.. versionchanged:: 20.0
Previously, the contents of this module were available through the (no longer existing)
module ``telegram._utils.helpers``.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import contextlib
import datetime as dtm
import time
from typing import TYPE_CHECKING, Optional, Union
if TYPE_CHECKING:
from telegram import Bot
UTC = dtm.timezone.utc
try:
import pytz
except ImportError:
pytz = None # type: ignore[assignment]
def localize(datetime: dtm.datetime, tzinfo: dtm.tzinfo) -> dtm.datetime:
"""Localize the datetime, both for pytz and zoneinfo timezones."""
if tzinfo is UTC:
return datetime.replace(tzinfo=UTC)
with contextlib.suppress(AttributeError):
# Since pytz might not be available, we need the suppress context manager
if isinstance(tzinfo, pytz.BaseTzInfo):
return tzinfo.localize(datetime)
if datetime.tzinfo is None:
return datetime.replace(tzinfo=tzinfo)
return datetime.astimezone(tzinfo)
def to_float_timestamp(
time_object: Union[float, dtm.timedelta, dtm.datetime, dtm.time],
reference_timestamp: Optional[float] = None,
tzinfo: Optional[dtm.tzinfo] = None,
) -> float:
"""
Converts a given time object to a float POSIX timestamp.
Used to convert different time specifications to a common format. The time object
can be relative (i.e. indicate a time increment, or a time of day) or absolute.
Objects from the :class:`datetime` module that are timezone-naive will be assumed
to be in UTC, if ``bot`` is not passed or ``bot.defaults`` is :obj:`None`.
Args:
time_object (:obj:`float` | :obj:`datetime.timedelta` | \
:obj:`datetime.datetime` | :obj:`datetime.time`):
Time value to convert. The semantics of this parameter will depend on its type:
* :obj:`float` will be interpreted as "seconds from :paramref:`reference_t`"
* :obj:`datetime.timedelta` will be interpreted as
"time increment from :paramref:`reference_timestamp`"
* :obj:`datetime.datetime` will be interpreted as an absolute date/time value
* :obj:`datetime.time` will be interpreted as a specific time of day
reference_timestamp (:obj:`float`, optional): POSIX timestamp that indicates the absolute
time from which relative calculations are to be performed (e.g. when
:paramref:`time_object` is given as an :obj:`int`, indicating "seconds from
:paramref:`reference_time`"). Defaults to now (the time at which this function is
called).
If :paramref:`time_object` is given as an absolute representation of date & time (i.e.
a :obj:`datetime.datetime` object), :paramref:`reference_timestamp` is not relevant
and so its value should be :obj:`None`. If this is not the case, a :exc:`ValueError`
will be raised.
tzinfo (:class:`datetime.tzinfo`, optional): If :paramref:`time_object` is a naive object
from the :mod:`datetime` module, it will be interpreted as this timezone. Defaults to
:attr:`datetime.timezone.utc` otherwise.
Note:
Only to be used by ``telegram.ext``.
Returns:
:obj:`float` | :obj:`None`:
The return value depends on the type of argument :paramref:`time_object`.
If :paramref:`time_object` is given as a time increment (i.e. as a :obj:`int`,
:obj:`float` or :obj:`datetime.timedelta`), then the return value will be
:paramref:`reference_timestamp` + :paramref:`time_object`.
Else if it is given as an absolute date/time value (i.e. a :obj:`datetime.datetime`
object), the equivalent value as a POSIX timestamp will be returned.
Finally, if it is a time of the day without date (i.e. a :obj:`datetime.time`
object), the return value is the nearest future occurrence of that time of day.
Raises:
TypeError: If :paramref:`time_object` s type is not one of those described above.
ValueError: If :paramref:`time_object` is a :obj:`datetime.datetime` and
:paramref:`reference_timestamp` is not :obj:`None`.
"""
if reference_timestamp is None:
reference_timestamp = time.time()
elif isinstance(time_object, dtm.datetime):
raise ValueError("t is an (absolute) datetime while reference_timestamp is not None")
if isinstance(time_object, dtm.timedelta):
return reference_timestamp + time_object.total_seconds()
if isinstance(time_object, (int, float)):
return reference_timestamp + time_object
if tzinfo is None:
# We do this here rather than in the signature to ensure that we can make calls like
# to_float_timestamp(
# time, tzinfo=bot.defaults.tzinfo if bot.defaults else None
# )
# This ensures clean separation of concerns, i.e. the default timezone should not be
# the responsibility of the caller
tzinfo = UTC
if isinstance(time_object, dtm.time):
reference_dt = dtm.datetime.fromtimestamp(
reference_timestamp, tz=time_object.tzinfo or tzinfo
)
reference_date = reference_dt.date()
reference_time = reference_dt.timetz()
aware_datetime = dtm.datetime.combine(reference_date, time_object)
if aware_datetime.tzinfo is None:
# datetime.combine uses the tzinfo of `time_object`, which might be None
# so we still need to localize
aware_datetime = localize(aware_datetime, tzinfo)
# if the time of day has passed today, use tomorrow
if reference_time > aware_datetime.timetz():
aware_datetime += dtm.timedelta(days=1)
return _datetime_to_float_timestamp(aware_datetime)
if isinstance(time_object, dtm.datetime):
if time_object.tzinfo is None:
time_object = localize(time_object, tzinfo)
return _datetime_to_float_timestamp(time_object)
raise TypeError(f"Unable to convert {type(time_object).__name__} object to timestamp")
def to_timestamp(
dt_obj: Union[float, dtm.timedelta, dtm.datetime, dtm.time, None],
reference_timestamp: Optional[float] = None,
tzinfo: Optional[dtm.tzinfo] = None,
) -> Optional[int]:
"""
Wrapper over :func:`to_float_timestamp` which returns an integer (the float value truncated
down to the nearest integer).
See the documentation for :func:`to_float_timestamp` for more details.
"""
return (
int(to_float_timestamp(dt_obj, reference_timestamp, tzinfo))
if dt_obj is not None
else None
)
def from_timestamp(
unixtime: Optional[int],
tzinfo: Optional[dtm.tzinfo] = None,
) -> Optional[dtm.datetime]:
"""
Converts an (integer) unix timestamp to a timezone aware datetime object.
:obj:`None` s are left alone (i.e. ``from_timestamp(None)`` is :obj:`None`).
Args:
unixtime (:obj:`int`): Integer POSIX timestamp.
tzinfo (:obj:`datetime.tzinfo`, optional): The timezone to which the timestamp is to be
converted to. Defaults to :obj:`None`, in which case the returned datetime object will
be timezone aware and in UTC.
Returns:
Timezone aware equivalent :obj:`datetime.datetime` value if :paramref:`unixtime` is not
:obj:`None`; else :obj:`None`.
"""
if unixtime is None:
return None
return dtm.datetime.fromtimestamp(unixtime, tz=UTC if tzinfo is None else tzinfo)
def extract_tzinfo_from_defaults(bot: Optional["Bot"]) -> Union[dtm.tzinfo, None]:
"""
Extracts the timezone info from the default values of the bot.
If the bot has no default values, :obj:`None` is returned.
"""
# We don't use `ininstance(bot, ExtBot)` here so that this works
# without the job-queue extra dependencies as well
if bot is None:
return None
if hasattr(bot, "defaults") and bot.defaults:
return bot.defaults.tzinfo
return None
def _datetime_to_float_timestamp(dt_obj: dtm.datetime) -> float:
"""
Converts a datetime object to a float timestamp (with sub-second precision).
If the datetime object is timezone-naive, it is assumed to be in UTC.
"""
if dt_obj.tzinfo is None:
dt_obj = dt_obj.replace(tzinfo=dtm.timezone.utc)
return dt_obj.timestamp()
@@ -1,148 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 DefaultValue class.
.. versionchanged:: 20.0
Previously, the contents of this module were available through the (no longer existing)
module ``telegram._utils.helpers``.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from typing import Generic, TypeVar, Union, overload
DVType = TypeVar("DVType", bound=object) # pylint: disable=invalid-name
OT = TypeVar("OT", bound=object)
class DefaultValue(Generic[DVType]):
"""Wrapper for immutable default arguments that allows to check, if the default value was set
explicitly. Usage::
default_one = DefaultValue(1)
def f(arg=default_one):
if arg is default_one:
print('`arg` is the default')
arg = arg.value
else:
print('`arg` was set explicitly')
print(f'`arg` = {str(arg)}')
This yields::
>>> f()
`arg` is the default
`arg` = 1
>>> f(1)
`arg` was set explicitly
`arg` = 1
>>> f(2)
`arg` was set explicitly
`arg` = 2
Also allows to evaluate truthiness::
default = DefaultValue(value)
if default:
...
is equivalent to::
default = DefaultValue(value)
if value:
...
``repr(DefaultValue(value))`` returns ``repr(value)`` and ``str(DefaultValue(value))`` returns
``f'DefaultValue({value})'``.
Args:
value (:class:`object`): The value of the default argument
Attributes:
value (:class:`object`): The value of the default argument
"""
__slots__ = ("value",)
def __init__(self, value: DVType):
self.value: DVType = value
def __bool__(self) -> bool:
return bool(self.value)
# This is mostly here for readability during debugging
def __str__(self) -> str:
return f"DefaultValue({self.value})"
# This is here to have the default instances nicely rendered in the docs
def __repr__(self) -> str:
return repr(self.value)
@overload
@staticmethod
def get_value(obj: "DefaultValue[OT]") -> OT: ...
@overload
@staticmethod
def get_value(obj: OT) -> OT: ...
@staticmethod
def get_value(obj: Union[OT, "DefaultValue[OT]"]) -> OT:
"""Shortcut for::
return obj.value if isinstance(obj, DefaultValue) else obj
Args:
obj (:obj:`object`): The object to process
Returns:
Same type as input, or the value of the input: The value
"""
return obj.value if isinstance(obj, DefaultValue) else obj
DEFAULT_NONE: DefaultValue[None] = DefaultValue(None)
""":class:`DefaultValue`: Default :obj:`None`"""
DEFAULT_FALSE: DefaultValue[bool] = DefaultValue(False)
""":class:`DefaultValue`: Default :obj:`False`"""
DEFAULT_TRUE: DefaultValue[bool] = DefaultValue(True)
""":class:`DefaultValue`: Default :obj:`True`
.. versionadded:: 20.0
"""
DEFAULT_20: DefaultValue[int] = DefaultValue(20)
""":class:`DefaultValue`: Default :obj:`20`"""
DEFAULT_IP: DefaultValue[str] = DefaultValue("127.0.0.1")
""":class:`DefaultValue`: Default :obj:`127.0.0.1`
.. versionadded:: 20.8
"""
DEFAULT_80: DefaultValue[int] = DefaultValue(80)
""":class:`DefaultValue`: Default :obj:`80`
.. versionadded:: 20.8
"""
@@ -1,73 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 auxiliary functionality for parsing MessageEntity objects.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from collections.abc import Sequence
from typing import Optional
from telegram._messageentity import MessageEntity
from telegram._utils.strings import TextEncoding
def parse_message_entity(text: str, entity: MessageEntity) -> str:
"""Returns the text from a given :class:`telegram.MessageEntity`.
Args:
text (:obj:`str`): The text to extract the entity from.
entity (:class:`telegram.MessageEntity`): The entity to extract the text from.
Returns:
:obj:`str`: The text of the given entity.
"""
entity_text = text.encode(TextEncoding.UTF_16_LE)
entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]
return entity_text.decode(TextEncoding.UTF_16_LE)
def parse_message_entities(
text: str, entities: Sequence[MessageEntity], types: Optional[Sequence[str]] = None
) -> dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
Args:
text (:obj:`str`): The text to extract the entity from.
entities (list[:class:`telegram.MessageEntity`]): The entities to extract the text from.
types (list[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
if types is None:
types = MessageEntity.ALL_TYPES
return {
entity: parse_message_entity(text, entity) for entity in entities if entity.type in types
}
@@ -1,90 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 helper functions related to enums.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import enum as _enum
import sys
from typing import TypeVar, Union
_A = TypeVar("_A")
_B = TypeVar("_B")
_Enum = TypeVar("_Enum", bound=_enum.Enum)
def get_member(enum_cls: type[_Enum], value: _A, default: _B) -> Union[_Enum, _A, _B]:
"""Tries to call ``enum_cls(value)`` to convert the value into an enumeration member.
If that fails, the ``default`` is returned.
"""
try:
return enum_cls(value)
except ValueError:
return default
# Python 3.11 and above has a different output for mixin classes for IntEnum, StrEnum and IntFlag
# see https://docs.python.org/3.11/library/enum.html#notes. We want e.g. str(StrEnumTest.FOO) to
# return "foo" instead of "StrEnumTest.FOO", which is not the case < py3.11
class StringEnum(str, _enum.Enum):
"""Helper class for string enums where ``str(member)`` prints the value, but ``repr(member)``
gives ``EnumName.MEMBER_NAME``.
"""
__slots__ = ()
def __repr__(self) -> str:
return f"<{self.__class__.__name__}.{self.name}>"
def __str__(self) -> str:
return str.__str__(self)
# Apply the __repr__ modification and __str__ fix to IntEnum
class IntEnum(_enum.IntEnum): # pylint: disable=invalid-slots
"""Helper class for int enums where ``str(member)`` prints the value, but ``repr(member)``
gives ``EnumName.MEMBER_NAME``.
"""
__slots__ = ()
def __repr__(self) -> str:
return f"<{self.__class__.__name__}.{self.name}>"
if sys.version_info < (3, 11):
def __str__(self) -> str:
return str(self.value)
class FloatEnum(float, _enum.Enum):
"""Helper class for float enums where ``str(member)`` prints the value, but ``repr(member)``
gives ``EnumName.MEMBER_NAME``.
"""
__slots__ = ()
def __repr__(self) -> str:
return f"<{self.__class__.__name__}.{self.name}>"
def __str__(self) -> str:
return str(self.value)
@@ -1,157 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 helper functions related to handling of files.
.. versionchanged:: 20.0
Previously, the contents of this module were available through the (no longer existing)
module ``telegram._utils.helpers``.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, Optional, TypeVar, Union, cast, overload
from telegram._utils.types import FileInput, FilePathInput
if TYPE_CHECKING:
from telegram import InputFile, TelegramObject
_T = TypeVar("_T", bound=Union[bytes, "InputFile", str, Path, None])
@overload
def load_file(obj: IO[bytes]) -> tuple[Optional[str], bytes]: ...
@overload
def load_file(obj: _T) -> tuple[None, _T]: ...
def load_file(
obj: Optional[FileInput],
) -> tuple[Optional[str], Union[bytes, "InputFile", str, Path, None]]:
"""If the input is a file handle, read the data and name and return it. Otherwise, return
the input unchanged.
"""
if obj is None:
return None, None
try:
contents = obj.read() # type: ignore[union-attr]
except AttributeError:
return None, cast(Union[bytes, "InputFile", str, Path], obj)
filename = guess_file_name(obj)
return filename, contents
def guess_file_name(obj: FileInput) -> Optional[str]:
"""If the input is a file handle, read name and return it. Otherwise, return
the input unchanged.
"""
if hasattr(obj, "name") and not isinstance(obj.name, int):
return Path(obj.name).name
return None
def is_local_file(obj: Optional[FilePathInput]) -> bool:
"""
Checks if a given string is a file on local system.
Args:
obj (:obj:`str`): The string to check.
"""
if obj is None:
return False
path = Path(obj)
try:
return path.is_file()
except Exception:
return False
def parse_file_input( # pylint: disable=too-many-return-statements
file_input: Union[FileInput, "TelegramObject"],
tg_type: Optional[type["TelegramObject"]] = None,
filename: Optional[str] = None,
attach: bool = False,
local_mode: bool = False,
) -> Union[str, "InputFile", Any]:
"""
Parses input for sending files:
* For string input, if the input is an absolute path of a local file:
* if ``local_mode`` is ``True``, adds the ``file://`` prefix. If the input is a relative
path of a local file, computes the absolute path and adds the ``file://`` prefix.
* if ``local_mode`` is ``False``, loads the file as binary data and builds an
:class:`InputFile` from that
Returns the input unchanged, otherwise.
* :class:`pathlib.Path` objects are treated the same way as strings.
* For IO and bytes input, returns an :class:`telegram.InputFile`.
* If :attr:`tg_type` is specified and the input is of that type, returns the ``file_id``
attribute.
Args:
file_input (:obj:`str` | :obj:`bytes` | :term:`file object` | :class:`~telegram.InputFile`\
| Telegram media object): The input to parse.
tg_type (:obj:`type`, optional): The Telegram media type the input can be. E.g.
:class:`telegram.Animation`.
filename (:obj:`str`, optional): The filename. Only relevant in case an
:class:`telegram.InputFile` is returned.
attach (:obj:`bool`, optional): Pass :obj:`True` if the parameter this file belongs to in
the request to Telegram should point to the multipart data via an ``attach://`` URI.
Defaults to `False`. Only relevant if an :class:`telegram.InputFile` is returned.
local_mode (:obj:`bool`, optional): Pass :obj:`True` if the bot is running an api server
in ``--local`` mode.
Returns:
:obj:`str` | :class:`telegram.InputFile` | :obj:`object`: The parsed input or the untouched
:attr:`file_input`, in case it's no valid file input.
"""
# Importing on file-level yields cyclic Import Errors
from telegram import InputFile # pylint: disable=import-outside-toplevel
if isinstance(file_input, str) and file_input.startswith("file://"):
if not local_mode:
raise ValueError("Specified file input is a file URI, but local mode is not enabled.")
return file_input
if isinstance(file_input, (str, Path)):
if is_local_file(file_input):
path = Path(file_input)
if local_mode:
return path.absolute().as_uri()
return InputFile(path.open(mode="rb"), filename=filename, attach=attach)
return file_input
if isinstance(file_input, bytes):
return InputFile(file_input, filename=filename, attach=attach)
if hasattr(file_input, "read"):
return InputFile(cast(IO, file_input), filename=filename, attach=attach)
if tg_type and isinstance(file_input, tg_type):
return file_input.file_id # type: ignore[attr-defined]
return file_input
@@ -1,50 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 helper functions related to logging.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import logging
from typing import Optional
def get_logger(file_name: str, class_name: Optional[str] = None) -> logging.Logger:
"""Returns a logger with an appropriate name.
Use as follows::
logger = get_logger(__name__)
If for example `__name__` is `telegram.ext._updater`, the logger will be named
`telegram.ext.Updater`. If `class_name` is passed, this will result in
`telegram.ext.<class_name>`. Useful e.g. for CamelCase class names.
If the file name points to a utils module, the logger name will simply be `telegram(.ext)`.
Returns:
:class:`logging.Logger`: The logger.
"""
parts = file_name.split("_")
if parts[1].startswith("utils") and class_name is None:
name = parts[0].rstrip(".")
else:
name = f"{parts[0]}{class_name or parts[1].capitalize()}"
return logging.getLogger(name)
@@ -1,49 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 a helper function for Telegram's ReplyMarkups
.. versionchanged:: 20.0
Previously, the contents of this module were available through the (no longer existing)
class ``telegram.ReplyMarkup``.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from collections.abc import Sequence
def check_keyboard_type(keyboard: object) -> bool:
"""Checks if the keyboard provided is of the correct type - A sequence of sequences.
Implicitly tested in the init-tests of `{Inline, Reply}KeyboardMarkup`
"""
# string and bytes may actually be used for ReplyKeyboardMarkup in which case each button
# would contain a single character. But that use case should be discouraged and we don't
# allow it here.
if not isinstance(keyboard, Sequence) or isinstance(keyboard, (str, bytes)):
return False
for row in keyboard:
if not isinstance(row, Sequence) or isinstance(row, (str, bytes)):
return False
for inner in row:
if isinstance(inner, Sequence) and not isinstance(inner, str):
return False
return True
@@ -1,45 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 auxiliary functionality for building strings for __repr__ method.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from typing import Any
def build_repr_with_selected_attrs(obj: object, **kwargs: Any) -> str:
"""Create ``__repr__`` string in the style ``Classname[arg1=1, arg2=2]``.
The square brackets emphasize the fact that an object cannot be instantiated
from this string.
Attributes that are to be used in the representation, are passed as kwargs.
"""
return (
f"{obj.__class__.__name__}"
# square brackets emphasize that an object cannot be instantiated with these params
f"[{', '.join(_stringify(name, value) for name, value in kwargs.items())}]"
)
def _stringify(key: str, val: Any) -> str:
return f"{key}={val.__qualname__ if callable(val) else val}"
@@ -1,55 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 a helper functions related to string manipulation.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from telegram._utils.enum import StringEnum
# TODO: Remove this when https://github.com/PyCQA/pylint/issues/6887 is resolved.
# pylint: disable=invalid-enum-extension
class TextEncoding(StringEnum):
"""This enum contains encoding schemes for text.
.. versionadded:: 21.5
"""
__slots__ = ()
UTF_8 = "utf-8"
UTF_16_LE = "utf-16-le"
def to_camel_case(snake_str: str) -> str:
"""Converts a snake_case string to camelCase.
Args:
snake_str (:obj:`str`): The string to convert.
Returns:
:obj:`str`: The converted string.
"""
components = snake_str.split("_")
return components[0] + "".join(x.title() for x in components[1:])
@@ -1,93 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 custom typing aliases for internal use within the library.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from collections.abc import Collection
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, Literal, Optional, TypeVar, Union
if TYPE_CHECKING:
from telegram import (
ForceReply,
InlineKeyboardMarkup,
InputFile,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from telegram._utils.defaultvalue import DefaultValue
FileLike = Union[IO[bytes], "InputFile"]
"""Either a bytes-stream (e.g. open file handler) or a :class:`telegram.InputFile`."""
FilePathInput = Union[str, Path]
"""A filepath either as string or as :obj:`pathlib.Path` object."""
FileInput = Union[FilePathInput, FileLike, bytes, str]
"""Valid input for passing files to Telegram. Either a file id as string, a file like object,
a local file path as string, :class:`pathlib.Path` or the file contents as :obj:`bytes`."""
JSONDict = dict[str, Any]
"""Dictionary containing response from Telegram or data to send to the API."""
DVValueType = TypeVar("DVValueType") # pylint: disable=invalid-name
DVType = Union[DVValueType, "DefaultValue[DVValueType]"]
"""Generic type for a variable which can be either `type` or `DefaultVaule[type]`."""
ODVInput = Optional[Union["DefaultValue[DVValueType]", DVValueType, "DefaultValue[None]"]]
"""Generic type for bot method parameters which can have defaults. ``ODVInput[type]`` is the same
as ``Optional[Union[DefaultValue[type], type, DefaultValue[None]]``."""
DVInput = Union["DefaultValue[DVValueType]", DVValueType, "DefaultValue[None]"]
"""Generic type for bot method parameters which can have defaults. ``DVInput[type]`` is the same
as ``Union[DefaultValue[type], type, DefaultValue[None]]``."""
RT = TypeVar("RT")
SCT = Union[RT, Collection[RT]] # pylint: disable=invalid-name
"""Single instance or collection of instances."""
ReplyMarkup = Union[
"InlineKeyboardMarkup", "ReplyKeyboardMarkup", "ReplyKeyboardRemove", "ForceReply"
]
"""Type alias for reply markup objects.
.. versionadded:: 20.0
"""
FieldTuple = tuple[str, Union[bytes, IO[bytes]], str]
"""Alias for return type of `InputFile.field_tuple`."""
UploadFileDict = dict[str, FieldTuple]
"""Dictionary containing file data to be uploaded to the API."""
HTTPVersion = Literal["1.1", "2.0", "2"]
"""Allowed HTTP versions.
.. versionadded:: 20.4"""
CorrectOptionID = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MarkdownVersion = Literal[1, 2]
SocketOpt = Union[
tuple[int, int, int],
tuple[int, int, Union[bytes, bytearray]],
tuple[int, int, None, int],
]
@@ -1,56 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 helper functions related to warnings issued by the library.
.. versionadded:: 20.0
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import warnings
from typing import Union
from telegram.warnings import PTBUserWarning
def warn(
message: Union[str, PTBUserWarning],
category: type[Warning] = PTBUserWarning,
stacklevel: int = 0,
) -> None:
"""
Helper function used as a shortcut for warning with default values.
.. versionadded:: 20.0
Args:
message (:obj:`str` | :obj:`PTBUserWarning`): Specify the warnings message to pass to
``warnings.warn()``.
.. versionchanged:: 21.2
Now also accepts a :obj:`PTBUserWarning` instance.
category (:obj:`type[Warning]`, optional): Specify the Warning class to pass to
``warnings.warn()``. Defaults to :class:`telegram.warnings.PTBUserWarning`.
stacklevel (:obj:`int`, optional): Specify the stacklevel to pass to ``warnings.warn()``.
Pass the same value as you'd pass directly to ``warnings.warn()``. Defaults to ``0``.
"""
warnings.warn(message, category=category, stacklevel=stacklevel + 1)
@@ -1,114 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 functionality used for transition warnings issued by this library.
It was created to prevent circular imports that would be caused by creating the warnings
inside warnings.py.
.. versionadded:: 20.2
"""
from typing import Any, Callable, Union
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning, PTBUserWarning
def build_deprecation_warning_message(
deprecated_name: str,
new_name: str,
object_type: str,
bot_api_version: str,
) -> str:
"""Builds a warning message for the transition in API when an object is renamed.
Returns a warning message that can be used in `warn` function.
"""
return (
f"The {object_type} '{deprecated_name}' was renamed to '{new_name}' in Bot API "
f"{bot_api_version}. We recommend using '{new_name}' instead of "
f"'{deprecated_name}'."
)
# Narrower type hints will cause linting errors and/or circular imports.
# We'll use `Any` here and put type hints in the calling code.
def warn_about_deprecated_arg_return_new_arg(
deprecated_arg: Any,
new_arg: Any,
deprecated_arg_name: str,
new_arg_name: str,
bot_api_version: str,
ptb_version: str,
stacklevel: int = 2,
warn_callback: Callable[[Union[str, PTBUserWarning], type[Warning], int], None] = warn,
) -> Any:
"""A helper function for the transition in API when argument is renamed.
Checks the `deprecated_arg` and `new_arg` objects; warns if non-None `deprecated_arg` object
was passed. Returns `new_arg` object (either the one originally passed by the user or the one
that user passed as `deprecated_arg`).
Raises `ValueError` if both `deprecated_arg` and `new_arg` objects were passed, and they are
different.
"""
if deprecated_arg and new_arg and deprecated_arg != new_arg:
base_message = build_deprecation_warning_message(
deprecated_name=deprecated_arg_name,
new_name=new_arg_name,
object_type="parameter",
bot_api_version=bot_api_version,
)
raise ValueError(
f"You passed different entities as '{deprecated_arg_name}' and '{new_arg_name}'. "
f"{base_message}"
)
if deprecated_arg:
warn_callback(
PTBDeprecationWarning(
ptb_version,
f"Bot API {bot_api_version} renamed the argument '{deprecated_arg_name}' to "
f"'{new_arg_name}'.",
),
stacklevel=stacklevel + 1, # type: ignore[call-arg]
)
return deprecated_arg
return new_arg
def warn_about_deprecated_attr_in_property(
deprecated_attr_name: str,
new_attr_name: str,
bot_api_version: str,
ptb_version: str,
stacklevel: int = 2,
) -> None:
"""A helper function for the transition in API when attribute is renamed. Call from properties.
The properties replace deprecated attributes in classes and issue these deprecation warnings.
"""
warn(
PTBDeprecationWarning(
ptb_version,
f"Bot API {bot_api_version} renamed the attribute '{deprecated_attr_name}' to "
f"'{new_attr_name}'.",
),
stacklevel=stacklevel + 1,
)
@@ -1,56 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 Final, NamedTuple
__all__ = ("__version__", "__version_info__")
class Version(NamedTuple):
"""Copies the behavior of sys.version_info.
serial is always 0 for stable releases.
"""
major: int
minor: int
micro: int
releaselevel: str # Literal['alpha', 'beta', 'candidate', 'final']
serial: int
def _rl_shorthand(self) -> str:
return {
"alpha": "a",
"beta": "b",
"candidate": "rc",
}[self.releaselevel]
def __str__(self) -> str:
version = f"{self.major}.{self.minor}"
if self.micro != 0:
version = f"{version}.{self.micro}"
if self.releaselevel != "final":
version = f"{version}{self._rl_shorthand()}{self.serial}"
return version
__version_info__: Final[Version] = Version(
major=21, minor=10, micro=0, releaselevel="final", serial=0
)
__version__: Final[str] = str(__version_info__)
@@ -1,195 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 objects related to Telegram video chats."""
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class VideoChatStarted(TelegramObject):
"""
This object represents a service message about a video
chat started in the chat. Currently holds no information.
.. versionadded:: 13.4
.. versionchanged:: 20.0
This class was renamed from ``VoiceChatStarted`` in accordance to Bot API 6.0.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(api_kwargs=api_kwargs)
self._freeze()
class VideoChatEnded(TelegramObject):
"""
This object represents a service message about a
video chat ended in the chat.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their
:attr:`duration` are equal.
.. versionadded:: 13.4
.. versionchanged:: 20.0
This class was renamed from ``VoiceChatEnded`` in accordance to Bot API 6.0.
Args:
duration (:obj:`int`): Voice chat duration in seconds.
Attributes:
duration (:obj:`int`): Voice chat duration in seconds.
"""
__slots__ = ("duration",)
def __init__(
self,
duration: int,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.duration: int = duration
self._id_attrs = (self.duration,)
self._freeze()
class VideoChatParticipantsInvited(TelegramObject):
"""
This object represents a service message about new members invited to a video chat.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their :attr:`users` are equal.
.. versionadded:: 13.4
.. versionchanged:: 20.0
This class was renamed from ``VoiceChatParticipantsInvited`` in accordance to Bot API 6.0.
Args:
users (Sequence[:class:`telegram.User`]): New members that were invited to the video chat.
.. versionchanged:: 20.0
|sequenceclassargs|
Attributes:
users (tuple[:class:`telegram.User`]): New members that were invited to the video chat.
.. versionchanged:: 20.0
|tupleclassattrs|
"""
__slots__ = ("users",)
def __init__(
self,
users: Sequence[User],
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.users: tuple[User, ...] = parse_sequence_arg(users)
self._id_attrs = (self.users,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["VideoChatParticipantsInvited"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["users"] = User.de_list(data.get("users", []), bot)
return super().de_json(data=data, bot=bot)
class VideoChatScheduled(TelegramObject):
"""This object represents a service message about a video chat scheduled in the chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`start_date` are equal.
.. versionchanged:: 20.0
This class was renamed from ``VoiceChatScheduled`` in accordance to Bot API 6.0.
Args:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
chat is supposed to be started by a chat administrator
.. versionchanged:: 20.3
|datetime_localization|
Attributes:
start_date (:obj:`datetime.datetime`): Point in time (Unix timestamp) when the video
chat is supposed to be started by a chat administrator
.. versionchanged:: 20.3
|datetime_localization|
"""
__slots__ = ("start_date",)
def __init__(
self,
start_date: dtm.datetime,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.start_date: dtm.datetime = start_date
self._id_attrs = (self.start_date,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["VideoChatScheduled"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["start_date"] = from_timestamp(data["start_date"], tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
@@ -1,64 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 WebAppData."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class WebAppData(TelegramObject):
"""Contains data sent from a `Web App <https://core.telegram.org/bots/webapps>`_ to the bot.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`data` and :attr:`button_text` are equal.
Examples:
:any:`Webapp Bot <examples.webappbot>`
.. versionadded:: 20.0
Args:
data (:obj:`str`): The data. Be aware that a bad client can send arbitrary data in this
field.
button_text (:obj:`str`): Text of the :paramref:`~telegram.KeyboardButton.web_app` keyboard
button, from which the Web App was opened.
Attributes:
data (:obj:`str`): The data. Be aware that a bad client can send arbitrary data in this
field.
button_text (:obj:`str`): Text of the :paramref:`~telegram.KeyboardButton.web_app` keyboard
button, from which the Web App was opened.
Warning:
Be aware that a bad client can send arbitrary data in this field.
"""
__slots__ = ("button_text", "data")
def __init__(self, data: str, button_text: str, *, api_kwargs: Optional[JSONDict] = None):
super().__init__(api_kwargs=api_kwargs)
# Required
self.data: str = data
self.button_text: str = button_text
self._id_attrs = (self.data, self.button_text)
self._freeze()
@@ -1,59 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 Web App Info."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class WebAppInfo(TelegramObject):
"""
This object contains information about a `Web App <https://core.telegram.org/bots/webapps>`_.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`url` are equal.
Examples:
:any:`Webapp Bot <examples.webappbot>`
.. versionadded:: 20.0
Args:
url (:obj:`str`): An HTTPS URL of a Web App to be opened with additional data as specified
in `Initializing Web Apps \
<https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
Attributes:
url (:obj:`str`): An HTTPS URL of a Web App to be opened with additional data as specified
in `Initializing Web Apps \
<https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
"""
__slots__ = ("url",)
def __init__(self, url: str, *, api_kwargs: Optional[JSONDict] = None):
super().__init__(api_kwargs=api_kwargs)
# Required
self.url: str = url
self._id_attrs = (self.url,)
self._freeze()
@@ -1,186 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 WebhookInfo."""
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class WebhookInfo(TelegramObject):
"""This object represents a Telegram WebhookInfo.
Contains information about the current status of a webhook.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`url`, :attr:`has_custom_certificate`,
:attr:`pending_update_count`, :attr:`ip_address`, :attr:`last_error_date`,
:attr:`last_error_message`, :attr:`max_connections`, :attr:`allowed_updates` and
:attr:`last_synchronization_error_date` are equal.
.. versionchanged:: 20.0
:attr:`last_synchronization_error_date` is considered as well when comparing objects of
this type in terms of equality.
Args:
url (:obj:`str`): Webhook URL, may be empty if webhook is not set up.
has_custom_certificate (:obj:`bool`): :obj:`True`, if a custom certificate was provided for
webhook certificate checks.
pending_update_count (:obj:`int`): Number of updates awaiting delivery.
ip_address (:obj:`str`, optional): Currently used webhook IP address.
last_error_date (:class:`datetime.datetime`): Optional. Datetime for the most recent
error that happened when trying to deliver an update via webhook.
.. versionchanged:: 20.3
|datetime_localization|
last_error_message (:obj:`str`, optional): Error message in human-readable format for the
most recent error that happened when trying to deliver an update via webhook.
max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS
connections to the webhook for update delivery.
allowed_updates (Sequence[:obj:`str`], optional): A sequence of update types the bot is
subscribed to. Defaults to all update types, except
:attr:`telegram.Update.chat_member`.
.. versionchanged:: 20.0
|sequenceclassargs|
last_synchronization_error_date (:class:`datetime.datetime`, optional): Datetime of the
most recent error that happened when trying to synchronize available updates with
Telegram datacenters.
.. versionadded:: 20.0
.. versionchanged:: 20.3
|datetime_localization|
Attributes:
url (:obj:`str`): Webhook URL, may be empty if webhook is not set up.
has_custom_certificate (:obj:`bool`): :obj:`True`, if a custom certificate was provided for
webhook certificate checks.
pending_update_count (:obj:`int`): Number of updates awaiting delivery.
ip_address (:obj:`str`): Optional. Currently used webhook IP address.
last_error_date (:class:`datetime.datetime`): Optional. Datetime for the most recent
error that happened when trying to deliver an update via webhook.
.. versionchanged:: 20.3
|datetime_localization|
last_error_message (:obj:`str`): Optional. Error message in human-readable format for the
most recent error that happened when trying to deliver an update via webhook.
max_connections (:obj:`int`): Optional. Maximum allowed number of simultaneous HTTPS
connections to the webhook for update delivery.
allowed_updates (tuple[:obj:`str`]): Optional. A tuple of update types the bot is
subscribed to. Defaults to all update types, except
:attr:`telegram.Update.chat_member`.
.. versionchanged:: 20.0
* |tupleclassattrs|
* |alwaystuple|
last_synchronization_error_date (:class:`datetime.datetime`, optional): Datetime of the
most recent error that happened when trying to synchronize available updates with
Telegram datacenters.
.. versionadded:: 20.0
.. versionchanged:: 20.3
|datetime_localization|
"""
__slots__ = (
"allowed_updates",
"has_custom_certificate",
"ip_address",
"last_error_date",
"last_error_message",
"last_synchronization_error_date",
"max_connections",
"pending_update_count",
"url",
)
def __init__(
self,
url: str,
has_custom_certificate: bool,
pending_update_count: int,
last_error_date: Optional[dtm.datetime] = None,
last_error_message: Optional[str] = None,
max_connections: Optional[int] = None,
allowed_updates: Optional[Sequence[str]] = None,
ip_address: Optional[str] = None,
last_synchronization_error_date: Optional[dtm.datetime] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.url: str = url
self.has_custom_certificate: bool = has_custom_certificate
self.pending_update_count: int = pending_update_count
# Optional
self.ip_address: Optional[str] = ip_address
self.last_error_date: Optional[dtm.datetime] = last_error_date
self.last_error_message: Optional[str] = last_error_message
self.max_connections: Optional[int] = max_connections
self.allowed_updates: tuple[str, ...] = parse_sequence_arg(allowed_updates)
self.last_synchronization_error_date: Optional[dtm.datetime] = (
last_synchronization_error_date
)
self._id_attrs = (
self.url,
self.has_custom_certificate,
self.pending_update_count,
self.ip_address,
self.last_error_date,
self.last_error_message,
self.max_connections,
self.allowed_updates,
self.last_synchronization_error_date,
)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["WebhookInfo"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["last_error_date"] = from_timestamp(data.get("last_error_date"), tzinfo=loc_tzinfo)
data["last_synchronization_error_date"] = from_timestamp(
data.get("last_synchronization_error_date"), tzinfo=loc_tzinfo
)
return super().de_json(data=data, bot=bot)
@@ -1,88 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 objects related to the write access allowed service message."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class WriteAccessAllowed(TelegramObject):
"""
This object represents a service message about a user allowing a bot to write messages after
adding it to the attachment menu, launching a Web App from a link, or accepting an explicit
request from a Web App sent by the method
`requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`web_app_name` is equal.
.. versionadded:: 20.0
.. versionchanged:: 20.6
Added custom equality comparison for objects of this class.
Args:
web_app_name (:obj:`str`, optional): Name of the Web App, if the access was granted when
the Web App was launched from a link.
.. versionadded:: 20.3
from_request (:obj:`bool`, optional): :obj:`True`, if the access was granted after the user
accepted an explicit request from a Web App sent by the method
`requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
.. versionadded:: 20.6
from_attachment_menu (:obj:`bool`, optional): :obj:`True`, if the access was granted when
the bot was added to the attachment or side menu.
.. versionadded:: 20.6
Attributes:
web_app_name (:obj:`str`): Optional. Name of the Web App, if the access was granted when
the Web App was launched from a link.
.. versionadded:: 20.3
from_request (:obj:`bool`): Optional. :obj:`True`, if the access was granted after the user
accepted an explicit request from a Web App.
.. versionadded:: 20.6
from_attachment_menu (:obj:`bool`): Optional. :obj:`True`, if the access was granted when
the bot was added to the attachment or side menu.
.. versionadded:: 20.6
"""
__slots__ = ("from_attachment_menu", "from_request", "web_app_name")
def __init__(
self,
web_app_name: Optional[str] = None,
from_request: Optional[bool] = None,
from_attachment_menu: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.web_app_name: Optional[str] = web_app_name
self.from_request: Optional[bool] = from_request
self.from_attachment_menu: Optional[bool] = from_attachment_menu
self._id_attrs = (self.web_app_name,)
self._freeze()
File diff suppressed because it is too large Load Diff
@@ -1,252 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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 classes that represent Telegram errors.
.. versionchanged:: 20.0
Replaced ``Unauthorized`` by :class:`Forbidden`.
"""
__all__ = (
"BadRequest",
"ChatMigrated",
"Conflict",
"EndPointNotFound",
"Forbidden",
"InvalidToken",
"NetworkError",
"PassportDecryptionError",
"RetryAfter",
"TelegramError",
"TimedOut",
)
from typing import Optional, Union
class TelegramError(Exception):
"""
Base class for Telegram errors.
Tip:
Objects of this type can be serialized via Python's :mod:`pickle` module and pickled
objects from one version of PTB are usually loadable in future versions. However, we can
not guarantee that this compatibility will always be provided. At least a manual one-time
conversion of the data may be needed on major updates of the library.
.. seealso:: :wiki:`Exceptions, Warnings and Logging <Exceptions%2C-Warnings-and-Logging>`
"""
__slots__ = ("message",)
def __init__(self, message: str):
super().__init__()
msg = message.removeprefix("Error: ")
msg = msg.removeprefix("[Error]: ")
msg = msg.removeprefix("Bad Request: ")
if msg != message:
# api_error - capitalize the msg...
msg = msg.capitalize()
self.message: str = msg
def __str__(self) -> str:
"""Gives the string representation of exceptions message.
Returns:
:obj:`str`
"""
return self.message
def __repr__(self) -> str:
"""Gives an unambiguous string representation of the exception.
Returns:
:obj:`str`
"""
return f"{self.__class__.__name__}('{self.message}')"
def __reduce__(self) -> tuple[type, tuple[str]]:
"""Defines how to serialize the exception for pickle.
.. seealso::
:py:meth:`object.__reduce__`, :mod:`pickle`.
Returns:
:obj:`tuple`
"""
return self.__class__, (self.message,)
class Forbidden(TelegramError):
"""Raised when the bot has not enough rights to perform the requested action.
Examples:
:any:`Raw API Bot <examples.rawapibot>`
.. versionchanged:: 20.0
This class was previously named ``Unauthorized``.
"""
__slots__ = ()
class InvalidToken(TelegramError):
"""Raised when the token is invalid.
Args:
message (:obj:`str`, optional): Any additional information about the exception.
.. versionadded:: 20.0
"""
__slots__ = ()
def __init__(self, message: Optional[str] = None) -> None:
super().__init__("Invalid token" if message is None else message)
class EndPointNotFound(TelegramError):
"""Raised when the requested endpoint is not found. Only relevant for
:meth:`telegram.Bot.do_api_request`.
.. versionadded:: 20.8
"""
__slots__ = ()
class NetworkError(TelegramError):
"""Base class for exceptions due to networking errors.
Tip:
This exception (and its subclasses) usually originates from the networking backend
used by :class:`~telegram.request.HTTPXRequest`, or a custom implementation of
:class:`~telegram.request.BaseRequest`. In this case, the original exception can be
accessed via the ``__cause__``
`attribute <https://docs.python.org/3/library/exceptions.html#exception-context>`_.
Examples:
:any:`Raw API Bot <examples.rawapibot>`
.. seealso::
:wiki:`Handling network errors <Handling-network-errors>`
"""
__slots__ = ()
class BadRequest(NetworkError):
"""Raised when Telegram could not process the request correctly."""
__slots__ = ()
class TimedOut(NetworkError):
"""Raised when a request took too long to finish.
.. seealso::
:wiki:`Handling network errors <Handling-network-errors>`
Args:
message (:obj:`str`, optional): Any additional information about the exception.
.. versionadded:: 20.0
"""
__slots__ = ()
def __init__(self, message: Optional[str] = None) -> None:
super().__init__(message or "Timed out")
class ChatMigrated(TelegramError):
"""
Raised when the requested group chat migrated to supergroup and has a new chat id.
.. seealso::
:wiki:`Storing Bot, User and Chat Related Data <Storing-bot%2C-user-and-chat-related-data>`
Args:
new_chat_id (:obj:`int`): The new chat id of the group.
Attributes:
new_chat_id (:obj:`int`): The new chat id of the group.
"""
__slots__ = ("new_chat_id",)
def __init__(self, new_chat_id: int):
super().__init__(f"Group migrated to supergroup. New chat id: {new_chat_id}")
self.new_chat_id: int = new_chat_id
def __reduce__(self) -> tuple[type, tuple[int]]: # type: ignore[override]
return self.__class__, (self.new_chat_id,)
class RetryAfter(TelegramError):
"""
Raised when flood limits where exceeded.
.. versionchanged:: 20.0
:attr:`retry_after` is now an integer to comply with the Bot API.
Args:
retry_after (:obj:`int`): Time in seconds, after which the bot can retry the request.
Attributes:
retry_after (:obj:`int`): Time in seconds, after which the bot can retry the request.
"""
__slots__ = ("retry_after",)
def __init__(self, retry_after: int):
super().__init__(f"Flood control exceeded. Retry in {retry_after} seconds")
self.retry_after: int = retry_after
def __reduce__(self) -> tuple[type, tuple[float]]: # type: ignore[override]
return self.__class__, (self.retry_after,)
class Conflict(TelegramError):
"""Raised when a long poll or webhook conflicts with another one."""
__slots__ = ()
def __reduce__(self) -> tuple[type, tuple[str]]:
return self.__class__, (self.message,)
class PassportDecryptionError(TelegramError):
"""Something went wrong with decryption.
.. versionchanged:: 20.0
This class was previously named ``TelegramDecryptionError`` and was available via
``telegram.TelegramDecryptionError``.
"""
__slots__ = ("_msg",)
def __init__(self, message: Union[str, Exception]):
super().__init__(f"PassportDecryptionError: {message}")
self._msg = str(message)
def __reduce__(self) -> tuple[type, tuple[str]]:
return self.__class__, (self._msg,)
@@ -1,104 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# 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/].
"""Extensions over the Telegram Bot API to facilitate bot making"""
__all__ = (
"AIORateLimiter",
"Application",
"ApplicationBuilder",
"ApplicationHandlerStop",
"BaseHandler",
"BasePersistence",
"BaseRateLimiter",
"BaseUpdateProcessor",
"BusinessConnectionHandler",
"BusinessMessagesDeletedHandler",
"CallbackContext",
"CallbackDataCache",
"CallbackQueryHandler",
"ChatBoostHandler",
"ChatJoinRequestHandler",
"ChatMemberHandler",
"ChosenInlineResultHandler",
"CommandHandler",
"ContextTypes",
"ConversationHandler",
"Defaults",
"DictPersistence",
"ExtBot",
"InlineQueryHandler",
"InvalidCallbackData",
"Job",
"JobQueue",
"MessageHandler",
"MessageReactionHandler",
"PaidMediaPurchasedHandler",
"PersistenceInput",
"PicklePersistence",
"PollAnswerHandler",
"PollHandler",
"PreCheckoutQueryHandler",
"PrefixHandler",
"ShippingQueryHandler",
"SimpleUpdateProcessor",
"StringCommandHandler",
"StringRegexHandler",
"TypeHandler",
"Updater",
"filters",
)
from . import filters
from ._aioratelimiter import AIORateLimiter
from ._application import Application, ApplicationHandlerStop
from ._applicationbuilder import ApplicationBuilder
from ._basepersistence import BasePersistence, PersistenceInput
from ._baseratelimiter import BaseRateLimiter
from ._baseupdateprocessor import BaseUpdateProcessor, SimpleUpdateProcessor
from ._callbackcontext import CallbackContext
from ._callbackdatacache import CallbackDataCache, InvalidCallbackData
from ._contexttypes import ContextTypes
from ._defaults import Defaults
from ._dictpersistence import DictPersistence
from ._extbot import ExtBot
from ._handlers.basehandler import BaseHandler
from ._handlers.businessconnectionhandler import BusinessConnectionHandler
from ._handlers.businessmessagesdeletedhandler import BusinessMessagesDeletedHandler
from ._handlers.callbackqueryhandler import CallbackQueryHandler
from ._handlers.chatboosthandler import ChatBoostHandler
from ._handlers.chatjoinrequesthandler import ChatJoinRequestHandler
from ._handlers.chatmemberhandler import ChatMemberHandler
from ._handlers.choseninlineresulthandler import ChosenInlineResultHandler
from ._handlers.commandhandler import CommandHandler
from ._handlers.conversationhandler import ConversationHandler
from ._handlers.inlinequeryhandler import InlineQueryHandler
from ._handlers.messagehandler import MessageHandler
from ._handlers.messagereactionhandler import MessageReactionHandler
from ._handlers.paidmediapurchasedhandler import PaidMediaPurchasedHandler
from ._handlers.pollanswerhandler import PollAnswerHandler
from ._handlers.pollhandler import PollHandler
from ._handlers.precheckoutqueryhandler import PreCheckoutQueryHandler
from ._handlers.prefixhandler import PrefixHandler
from ._handlers.shippingqueryhandler import ShippingQueryHandler
from ._handlers.stringcommandhandler import StringCommandHandler
from ._handlers.stringregexhandler import StringRegexHandler
from ._handlers.typehandler import TypeHandler
from ._jobqueue import Job, JobQueue
from ._picklepersistence import PicklePersistence
from ._updater import Updater

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