From 1c99096e0616e21c76a3292e4be6d2700aa3b8a3 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 24 Nov 2024 14:48:28 +0100 Subject: [PATCH 1/5] Add `PreparedInlineMessage` --- docs/source/telegram.inline-tree.rst | 1 + .../source/telegram.preparedinlinemessage.rst | 6 + telegram/__init__.py | 2 + telegram/_inline/preparedinlinemessage.py | 83 ++++++++++++++ tests/_inline/test_preparedinlinemessage.py | 108 ++++++++++++++++++ 5 files changed, 200 insertions(+) create mode 100644 docs/source/telegram.preparedinlinemessage.rst create mode 100644 telegram/_inline/preparedinlinemessage.py create mode 100644 tests/_inline/test_preparedinlinemessage.py diff --git a/docs/source/telegram.inline-tree.rst b/docs/source/telegram.inline-tree.rst index c187219e0f6..c21b3c33828 100644 --- a/docs/source/telegram.inline-tree.rst +++ b/docs/source/telegram.inline-tree.rst @@ -42,3 +42,4 @@ To enable this option, send the ``/setinline`` command to `@BotFather +# +# 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.""" +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: Optional[dtm.datetime] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.id: str = id + self.expiration_date: Optional[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) diff --git a/tests/_inline/test_preparedinlinemessage.py b/tests/_inline/test_preparedinlinemessage.py new file mode 100644 index 00000000000..fb341eb0b99 --- /dev/null +++ b/tests/_inline/test_preparedinlinemessage.py @@ -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 +# +# 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) + c = PreparedInlineMessage(self.id, self.expiration_date) + d = PreparedInlineMessage("other_uid") + 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) From 5a0b72f191bf3a14032b9b1d08ceaba8f03c2b65 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 24 Nov 2024 14:50:29 +0100 Subject: [PATCH 2/5] Make `PreparedInlineMessage.expiraten_date` Required --- telegram/_inline/preparedinlinemessage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telegram/_inline/preparedinlinemessage.py b/telegram/_inline/preparedinlinemessage.py index a467610e860..2525ee2643f 100644 --- a/telegram/_inline/preparedinlinemessage.py +++ b/telegram/_inline/preparedinlinemessage.py @@ -54,13 +54,13 @@ class PreparedInlineMessage(TelegramObject): def __init__( self, id: str, # pylint: disable=redefined-builtin - expiration_date: Optional[dtm.datetime] = None, + expiration_date: dtm.datetime, *, api_kwargs: Optional[JSONDict] = None, ): super().__init__(api_kwargs=api_kwargs) self.id: str = id - self.expiration_date: Optional[dtm.datetime] = expiration_date + self.expiration_date: dtm.datetime = expiration_date self._id_attrs = (self.id,) From 57398547da9ef8823b61538fc769c04259fed7b7 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:12:17 +0100 Subject: [PATCH 3/5] Add `Bot.save_prepared_inline_message` --- docs/source/inclusions/bot_methods.rst | 2 + telegram/_bot.py | 62 ++++++++++++++++++++++++++ telegram/ext/_extbot.py | 32 +++++++++++++ tests/test_bot.py | 10 +++++ 4 files changed, 106 insertions(+) diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst index 3189de1c1d3..e565ee58e39 100644 --- a/docs/source/inclusions/bot_methods.rst +++ b/docs/source/inclusions/bot_methods.rst @@ -375,6 +375,8 @@ - Used for obtaining the bot's Telegram Stars transactions * - :meth:`~telegram.Bot.refund_star_payment` - Used for refunding a payment in Telegram Stars + * - :meth:`~telegram.Bot.save_prepared_inline_message` + - Used for storing a message to be sent by a user of a Mini App .. raw:: html diff --git a/telegram/_bot.py b/telegram/_bot.py index cc2ba38fb3a..1f9cd3fc267 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -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 @@ -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, @@ -9531,6 +9591,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 diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 66921e415df..1213c7b20ab 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -69,6 +69,7 @@ MessageId, PhotoSize, Poll, + PreparedInlineMessage, ReactionType, ReplyParameters, SentWebAppMessage, @@ -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, @@ -4379,6 +4410,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 diff --git a/tests/test_bot.py b/tests/test_bot.py index 8ff0dec8d7b..7117f4229a0 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -67,6 +67,7 @@ MessageEntity, Poll, PollOption, + PreparedInlineMessage, ReactionTypeCustomEmoji, ReactionTypeEmoji, ReplyParameters, @@ -2841,6 +2842,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 From b359aa49de2cc3cf12e19c2e47337fc113841ddf Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:35:05 +0100 Subject: [PATCH 4/5] Fix tests --- tests/_inline/test_preparedinlinemessage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/_inline/test_preparedinlinemessage.py b/tests/_inline/test_preparedinlinemessage.py index fb341eb0b99..c20a3135cce 100644 --- a/tests/_inline/test_preparedinlinemessage.py +++ b/tests/_inline/test_preparedinlinemessage.py @@ -90,9 +90,9 @@ def test_to_dict(self, prepared_inline_message): def test_equality(self, prepared_inline_message): a = prepared_inline_message - b = PreparedInlineMessage(self.id) - c = PreparedInlineMessage(self.id, self.expiration_date) - d = PreparedInlineMessage("other_uid") + 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 From b0209c39ec8fa8e1b94ccf789f37ac57b6361f75 Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 24 Nov 2024 21:35:57 +0100 Subject: [PATCH 5/5] Fix module docstring --- telegram/_inline/preparedinlinemessage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/_inline/preparedinlinemessage.py b/telegram/_inline/preparedinlinemessage.py index 2525ee2643f..cc4078d2d4f 100644 --- a/telegram/_inline/preparedinlinemessage.py +++ b/telegram/_inline/preparedinlinemessage.py @@ -16,7 +16,7 @@ # # 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.""" +"""This module contains an object that represents a Telegram Prepared inline Message.""" import datetime as dtm from typing import TYPE_CHECKING, Optional pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy