Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Bot API 8.0: PreparedInlineMessage and Bot.save_prepared_inline_message #4574

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 6 commits into from
Nov 28, 2024
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
2 changes: 2 additions & 0 deletions docs/source/inclusions/bot_methods.rst
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,8 @@
- Used for getting basic info about a file
* - :meth:`~telegram.Bot.get_me`
- Used for getting basic information about the bot
* - :meth:`~telegram.Bot.save_prepared_inline_message`
- Used for storing a message to be sent by a user of a Mini App

.. raw:: html

Expand Down
1 change: 1 addition & 0 deletions docs/source/telegram.inline-tree.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ To enable this option, send the ``/setinline`` command to `@BotFather <https://t
telegram.inputvenuemessagecontent
telegram.inputcontactmessagecontent
telegram.inputinvoicemessagecontent
telegram.preparedinlinemessage
6 changes: 6 additions & 0 deletions docs/source/telegram.preparedinlinemessage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
PreparedInlineMessage
=====================

.. autoclass:: telegram.PreparedInlineMessage
:members:
:show-inheritance:
2 changes: 2 additions & 0 deletions telegram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
"PollAnswer",
"PollOption",
"PreCheckoutQuery",
"PreparedInlineMessage",
"ProximityAlertTriggered",
"ReactionCount",
"ReactionType",
Expand Down Expand Up @@ -405,6 +406,7 @@
from ._inline.inputmessagecontent import InputMessageContent
from ._inline.inputtextmessagecontent import InputTextMessageContent
from ._inline.inputvenuemessagecontent import InputVenueMessageContent
from ._inline.preparedinlinemessage import PreparedInlineMessage
from ._keyboardbutton import KeyboardButton
from ._keyboardbuttonpolltype import KeyboardButtonPollType
from ._keyboardbuttonrequest import KeyboardButtonRequestChat, KeyboardButtonRequestUsers
Expand Down
62 changes: 62 additions & 0 deletions telegram/_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
from telegram._forumtopic import ForumTopic
from telegram._games.gamehighscore import GameHighScore
from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from telegram._inline.preparedinlinemessage import PreparedInlineMessage
from telegram._menubutton import MenuButton
from telegram._message import Message
from telegram._messageid import MessageId
Expand Down Expand Up @@ -3641,6 +3642,65 @@ async def answer_inline_query(
api_kwargs=api_kwargs,
)

async def save_prepared_inline_message(
self,
user_id: int,
result: "InlineQueryResult",
allow_user_chats: Optional[bool] = None,
allow_bot_chats: Optional[bool] = None,
allow_group_chats: Optional[bool] = None,
allow_channel_chats: 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,
) -> PreparedInlineMessage:
"""Stores a message that can be sent by a user of a Mini App.

.. versionadded:: NEXT.VERSION

Args:
user_id (:obj:`int`): Unique identifier of the target user that can use the prepared
message.
result (:class:`telegram.InlineQueryResult`): The result to store.
allow_user_chats (:obj:`bool`, optional): Pass :obj:`True` if the message can be sent
to private chats with users
allow_bot_chats (:obj:`bool`, optional): Pass :obj:`True` if the message can be sent
to private chats with bots
allow_group_chats (:obj:`bool`, optional): Pass :obj:`True` if the message can be sent
to group and supergroup chats
allow_channel_chats (:obj:`bool`, optional): Pass :obj:`True` if the message can be
sent to channels

Returns:
:class:`telegram.PreparedInlineMessage`: On success, the prepared message is returned.

Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"user_id": user_id,
"result": result,
"allow_user_chats": allow_user_chats,
"allow_bot_chats": allow_bot_chats,
"allow_group_chats": allow_group_chats,
"allow_channel_chats": allow_channel_chats,
}
return PreparedInlineMessage.de_json( # type: ignore[return-value]
await self._post(
"savePreparedInlineMessage",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
),
self,
)

async def get_user_profile_photos(
self,
user_id: int,
Expand Down Expand Up @@ -9596,6 +9656,8 @@ def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002
"""Alias for :meth:`send_chat_action`"""
answerInlineQuery = answer_inline_query
"""Alias for :meth:`answer_inline_query`"""
savePreparedInlineMessage = save_prepared_inline_message
"""Alias for :meth:`save_prepared_inline_message`"""
getUserProfilePhotos = get_user_profile_photos
"""Alias for :meth:`get_user_profile_photos`"""
getFile = get_file
Expand Down
83 changes: 83 additions & 0 deletions telegram/_inline/preparedinlinemessage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <[email protected]>
#
# 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 Prepared inline Message."""
import datetime as dtm
from typing import TYPE_CHECKING, Optional

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 PreparedInlineMessage(TelegramObject):
"""Describes an inline message to be sent by a user of a Mini App.

Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal.

.. versionadded:: NEXT.VERSION

Args:
id (:obj:`str`): Unique identifier of the prepared message
expiration_date (:class:`datetime.datetime`): Expiration date of the prepared message.
Expired prepared messages can no longer be used.
|datetime_localization|

Attributes:
id (:obj:`str`): Unique identifier of the prepared message
expiration_date (:class:`datetime.datetime`): Expiration date of the prepared message.
Expired prepared messages can no longer be used.
|datetime_localization|
"""

__slots__ = ("expiration_date", "id")

def __init__(
self,
id: str, # pylint: disable=redefined-builtin
expiration_date: dtm.datetime,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.expiration_date: dtm.datetime = expiration_date

self._id_attrs = (self.id,)

self._freeze()

@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PreparedInlineMessage"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)

if data is None:
return None

# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["expiration_date"] = from_timestamp(data.get("expiration_date"), tzinfo=loc_tzinfo)

return super().de_json(data=data, bot=bot)
32 changes: 32 additions & 0 deletions telegram/ext/_extbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
MessageId,
PhotoSize,
Poll,
PreparedInlineMessage,
ReactionType,
ReplyParameters,
SentWebAppMessage,
Expand Down Expand Up @@ -979,6 +980,36 @@ async def answer_inline_query(
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)

async def save_prepared_inline_message(
self,
user_id: int,
result: "InlineQueryResult",
allow_user_chats: Optional[bool] = None,
allow_bot_chats: Optional[bool] = None,
allow_group_chats: Optional[bool] = None,
allow_channel_chats: 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,
rate_limit_args: Optional[RLARGS] = None,
) -> PreparedInlineMessage:
return await super().save_prepared_inline_message(
user_id=user_id,
result=result,
allow_user_chats=allow_user_chats,
allow_bot_chats=allow_bot_chats,
allow_group_chats=allow_group_chats,
allow_channel_chats=allow_channel_chats,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)

async def answer_pre_checkout_query(
self,
pre_checkout_query_id: str,
Expand Down Expand Up @@ -4407,6 +4438,7 @@ async def edit_chat_subscription_invite_link(
sendGame = send_game
sendChatAction = send_chat_action
answerInlineQuery = answer_inline_query
savePreparedInlineMessage = save_prepared_inline_message
getUserProfilePhotos = get_user_profile_photos
getFile = get_file
banChatMember = ban_chat_member
Expand Down
108 changes: 108 additions & 0 deletions tests/_inline/test_preparedinlinemessage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime as dtm

import pytest

from telegram import Location, PreparedInlineMessage
from telegram._utils.datetime import UTC, to_timestamp
from tests.auxil.slots import mro_slots


@pytest.fixture(scope="module")
def prepared_inline_message():
return PreparedInlineMessage(
PreparedInlineMessageTestBase.id,
PreparedInlineMessageTestBase.expiration_date,
)


class PreparedInlineMessageTestBase:
id = "some_uid"
expiration_date = dtm.datetime.now(tz=UTC).replace(microsecond=0)


class TestPreparedInlineMessageWithoutRequest(PreparedInlineMessageTestBase):
def test_slot_behaviour(self, prepared_inline_message):
inst = prepared_inline_message
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"

def test_expected_values(self, prepared_inline_message):
assert prepared_inline_message.id == self.id
assert prepared_inline_message.expiration_date == self.expiration_date

def test_de_json(self, prepared_inline_message):
json_dict = {
"id": self.id,
"expiration_date": to_timestamp(self.expiration_date),
}
new_prepared_inline_message = PreparedInlineMessage.de_json(json_dict, None)

assert isinstance(new_prepared_inline_message, PreparedInlineMessage)
assert new_prepared_inline_message.id == prepared_inline_message.id
assert (
new_prepared_inline_message.expiration_date == prepared_inline_message.expiration_date
)

def test_de_json_localization(self, offline_bot, raw_bot, tz_bot):
json_dict = {
"id": "some_uid",
"expiration_date": to_timestamp(self.expiration_date),
}
pim = PreparedInlineMessage.de_json(json_dict, offline_bot)
pim_raw = PreparedInlineMessage.de_json(json_dict, raw_bot)
pim_tz = PreparedInlineMessage.de_json(json_dict, tz_bot)

# comparing utcoffset because comparing tzinfo objects is not reliable
offset = pim_tz.expiration_date.utcoffset()
offset_tz = tz_bot.defaults.tzinfo.utcoffset(pim_tz.expiration_date.replace(tzinfo=None))

assert pim.expiration_date.tzinfo == UTC
assert pim_raw.expiration_date.tzinfo == UTC
assert offset_tz == offset

def test_to_dict(self, prepared_inline_message):
prepared_inline_message_dict = prepared_inline_message.to_dict()

assert isinstance(prepared_inline_message_dict, dict)
assert prepared_inline_message_dict["id"] == prepared_inline_message.id
assert prepared_inline_message_dict["expiration_date"] == to_timestamp(
self.expiration_date
)

def test_equality(self, prepared_inline_message):
a = prepared_inline_message
b = PreparedInlineMessage(self.id, self.expiration_date)
c = PreparedInlineMessage(self.id, self.expiration_date + dtm.timedelta(seconds=1))
d = PreparedInlineMessage("other_uid", self.expiration_date)
e = Location(123, 456)

assert a == b
assert hash(a) == hash(b)

assert a == c
assert hash(a) == hash(c)

assert a != d
assert hash(a) != hash(d)

assert a != e
assert hash(a) != hash(e)
10 changes: 10 additions & 0 deletions tests/test_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
MessageEntity,
Poll,
PollOption,
PreparedInlineMessage,
ReactionTypeCustomEmoji,
ReactionTypeEmoji,
ReplyParameters,
Expand Down Expand Up @@ -2857,6 +2858,15 @@ async def test_answer_inline_query_current_offset_error(self, bot, inline_result
1234, results=inline_results, next_offset=42, current_offset=51
)

async def test_save_prepared_inline_message(self, bot, chat_id):
# We can't really check that the result is stored correctly, we just ensur ethat we get
# a proper return value
result = InlineQueryResultArticle(
id="some_id", title="title", input_message_content=InputTextMessageContent("text")
)
out = await bot.save_prepared_inline_message(chat_id, result, True, False, True, False)
assert isinstance(out, PreparedInlineMessage)

async def test_get_user_profile_photos(self, bot, chat_id):
user_profile_photos = await bot.get_user_profile_photos(chat_id)
assert user_profile_photos.photos[0][0].file_size == 5403
Expand Down
Loading