Skip to content

Api 9.0 profile photos #4766

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changes/unreleased/4756.JT5nmUmGRG6qDEh5ScMn5f.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ closes_threads = []
uid = "4763"
author_uid = "aelkheir"
closes_threads = []
[[pull_requests]]
uid = "4766"
author_uid = "Bibo-Joshi"
4 changes: 4 additions & 0 deletions docs/source/inclusions/bot_methods.rst
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@
- Used for marking a message as read.
* - :meth:`~telegram.Bot.delete_business_messages`
- Used for deleting business messages.
* - :meth:`~telegram.Bot.remove_business_account_profile_photo`
- Used for removing the business accounts profile photo
* - :meth:`~telegram.Bot.set_business_account_name`
- Used for setting the business account name.
* - :meth:`~telegram.Bot.set_business_account_username`
Expand All @@ -425,6 +427,8 @@
- Used for setting the business account bio.
* - :meth:`~telegram.Bot.set_business_account_gift_settings`
- Used for setting the business account gift settings.
* - :meth:`~telegram.Bot.set_business_account_profile_photo`
- Used for setting the business accounts profile photo
* - :meth:`~telegram.Bot.convert_gift_to_stars`
- Used for converting owned reqular gifts to stars.
* - :meth:`~telegram.Bot.upgrade_gift`
Expand Down
3 changes: 3 additions & 0 deletions docs/source/telegram.at-tree.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ Available Types
telegram.inputpaidmedia
telegram.inputpaidmediaphoto
telegram.inputpaidmediavideo
telegram.inputprofilephoto
telegram.inputprofilephotoanimated
telegram.inputprofilephotostatic
telegram.inputpolloption
telegram.keyboardbutton
telegram.keyboardbuttonpolltype
Expand Down
6 changes: 6 additions & 0 deletions docs/source/telegram.inputprofilephoto.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
InputProfilePhoto
=================

.. autoclass:: telegram.InputProfilePhoto
:members:
:show-inheritance:
6 changes: 6 additions & 0 deletions docs/source/telegram.inputprofilephotoanimated.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
InputProfilePhotoAnimated
=========================

.. autoclass:: telegram.InputProfilePhotoAnimated
:members:
:show-inheritance:
6 changes: 6 additions & 0 deletions docs/source/telegram.inputprofilephotostatic.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
InputProfilePhotoStatic
=======================

.. autoclass:: telegram.InputProfilePhotoStatic
:members:
:show-inheritance:
8 changes: 8 additions & 0 deletions telegram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
"InputPaidMediaPhoto",
"InputPaidMediaVideo",
"InputPollOption",
"InputProfilePhoto",
"InputProfilePhotoAnimated",
"InputProfilePhotoStatic",
"InputSticker",
"InputTextMessageContent",
"InputVenueMessageContent",
Expand Down Expand Up @@ -384,6 +387,11 @@
InputPaidMediaPhoto,
InputPaidMediaVideo,
)
from ._files.inputprofilephoto import (
InputProfilePhoto,
InputProfilePhotoAnimated,
InputProfilePhotoStatic,
)
from ._files.inputsticker import InputSticker
from ._files.location import Location
from ._files.photosize import PhotoSize
Expand Down
98 changes: 98 additions & 0 deletions telegram/_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputProfilePhoto,
InputSticker,
LabeledPrice,
LinkPreviewOptions,
Expand Down Expand Up @@ -9753,6 +9754,99 @@ async def set_business_account_gift_settings(
api_kwargs=api_kwargs,
)

async def set_business_account_profile_photo(
self,
business_connection_id: str,
photo: "InputProfilePhoto",
is_public: Optional[bool] = 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:
"""
Changes the profile photo of a managed business account.
Requires the :attr:`~telegram.BusinessBotRights.can_edit_profile_photo` business
bot right.

.. versionadded:: NEXT.VERSION

Args:
business_connection_id (:obj:`str`): Unique identifier of the business connection.
photo (:class:`telegram.InputProfilePhoto`): The new profile photo to set.
is_public (:obj:`bool`, optional): Pass :obj:`True` to set the public photo, which will
be visible even if the main photo is hidden by the business account's privacy
settings. An account can have only one public photo.

Returns:
:obj:`bool`: On success, :obj:`True` is returned.

Raises:
:class:`telegram.error.TelegramError`

"""
data: JSONDict = {
"business_connection_id": business_connection_id,
"photo": photo,
"is_public": is_public,
}
return await self._post(
"setBusinessAccountProfilePhoto",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)

async def remove_business_account_profile_photo(
self,
business_connection_id: str,
is_public: Optional[bool] = 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:
"""
Removes the current profile photo of a managed business account.
Requires the :attr:`~telegram.BusinessBotRights.can_edit_profile_photo` business
bot right.

.. versionadded:: NEXT.VERSION

Args:
business_connection_id (:obj:`str`): Unique identifier of the business connection.
is_public (:obj:`bool`, optional): Pass :obj:`True` to remove the public photo, which
will be visible even if the main photo is hidden by the business account's privacy
settings. After the main photo is removed, the previous profile photo (if present)
becomes the main photo.

Returns:
:obj:`bool`: On success, :obj:`True` is returned.

Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"business_connection_id": business_connection_id,
"is_public": is_public,
}
return await self._post(
"removeBusinessAccountProfilePhoto",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)

async def convert_gift_to_stars(
self,
business_connection_id: str,
Expand Down Expand Up @@ -10861,6 +10955,10 @@ def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002
"""Alias for :meth:`set_business_account_bio`"""
setBusinessAccountGiftSettings = set_business_account_gift_settings
"""Alias for :meth:`set_business_account_gift_settings`"""
setBusinessAccountProfilePhoto = set_business_account_profile_photo
"""Alias for :meth:`set_business_account_profile_photo`"""
removeBusinessAccountProfilePhoto = remove_business_account_profile_photo
"""Alias for :meth:`remove_business_account_profile_photo`"""
convertGiftToStars = convert_gift_to_stars
"""Alias for :meth:`convert_gift_to_stars`"""
upgradeGift = upgrade_gift
Expand Down
142 changes: 142 additions & 0 deletions telegram/_files/inputprofilephoto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/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 objects that represents a InputProfilePhoto and subclasses."""

import datetime as dtm
from typing import TYPE_CHECKING, Optional, Union

from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.files import parse_file_input
from telegram._utils.types import FileInput, JSONDict

if TYPE_CHECKING:
from telegram import InputFile


class InputProfilePhoto(TelegramObject):
"""This object describes a profile photo to set. Currently, it can be one of

* :class:`InputProfilePhotoStatic`
* :class:`InputProfilePhotoAnimated`

.. versionadded:: NEXT.VERSION

Args:
type (:obj:`str`): Type of the profile photo.

Attributes:
type (:obj:`str`): Type of the profile photo.

"""

STATIC = constants.InputProfilePhotoType.STATIC
""":obj:`str`: :tg-const:`telegram.constants.InputProfilePhotoType.STATIC`."""
ANIMATED = constants.InputProfilePhotoType.ANIMATED
""":obj:`str`: :tg-const:`telegram.constants.InputProfilePhotoType.ANIMATED`."""

__slots__ = ("type",)

def __init__(
self,
type: str, # pylint: disable=redefined-builtin
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.type: str = enum.get_member(constants.InputProfilePhotoType, type, type)

self._freeze()


class InputProfilePhotoStatic(InputProfilePhoto):
"""A static profile photo in the .JPG format.

.. versionadded:: NEXT.VERSION

Args:
photo (:term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
:class:`pathlib.Path`): The static profile photo. |uploadinputnopath|

Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputProfilePhotoType.STATIC`.
photo (:class:`telegram.InputFile` | :obj:`str`): The static profile photo.

"""

__slots__ = ("photo",)

def __init__(
self,
photo: FileInput,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=constants.InputProfilePhotoType.STATIC, api_kwargs=api_kwargs)
with self._unfrozen():
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
self.photo: Union[str, InputFile] = parse_file_input(
photo, attach=True, local_mode=True
)


class InputProfilePhotoAnimated(InputProfilePhoto):
"""An animated profile photo in the MPEG4 format.

.. versionadded:: NEXT.VERSION

Args:
animation (:term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \
:class:`pathlib.Path`): The animated profile photo. |uploadinputnopath|
main_frame_timestamp (:class:`datetime.timedelta` | :obj:`int` | :obj:`float`, optional):
Timestamp in seconds of the frame that will be used as the static profile photo.
Defaults to ``0.0``.

Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputProfilePhotoType.ANIMATED`.
animation (:class:`telegram.InputFile` | :obj:`str`): The animated profile photo.
main_frame_timestamp (:class:`datetime.timedelta`): Optional. Timestamp in seconds of the
frame that will be used as the static profile photo. Defaults to ``0.0``.
"""

__slots__ = ("animation", "main_frame_timestamp")

def __init__(
self,
animation: FileInput,
main_frame_timestamp: Union[float, dtm.timedelta, None] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(type=constants.InputProfilePhotoType.ANIMATED, api_kwargs=api_kwargs)
with self._unfrozen():
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
self.animation: Union[str, InputFile] = parse_file_input(
animation, attach=True, local_mode=True
)

if isinstance(main_frame_timestamp, dtm.timedelta):
self.main_frame_timestamp: Optional[dtm.timedelta] = main_frame_timestamp
elif main_frame_timestamp is None:
self.main_frame_timestamp = None
else:
self.main_frame_timestamp = dtm.timedelta(seconds=main_frame_timestamp)
16 changes: 16 additions & 0 deletions telegram/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"InlineQueryResultsButtonLimit",
"InputMediaType",
"InputPaidMediaType",
"InputProfilePhotoType",
"InvoiceLimit",
"KeyboardButtonRequestUsersLimit",
"LocationLimit",
Expand Down Expand Up @@ -1419,6 +1420,21 @@ class InputPaidMediaType(StringEnum):
""":obj:`str`: Type of :class:`telegram.InputMediaVideo`."""


class InputProfilePhotoType(StringEnum):
"""This enum contains the available types of :class:`telegram.InputProfilePhoto`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.

.. versionadded:: NEXT.VERSION
"""

__slots__ = ()

STATIC = "static"
""":obj:`str`: Type of :class:`telegram.InputProfilePhotoStatic`."""
ANIMATED = "animated"
""":obj:`str`: Type of :class:`telegram.InputProfilePhotoAnimated`."""


class InlineQueryLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.InlineQuery`/
:meth:`telegram.Bot.answer_inline_query`. The enum members of this enumeration are instances
Expand Down
Loading
Loading