From bad6d8613a05257a5171f6aa7c7e8f18226d615c Mon Sep 17 00:00:00 2001 From: Aryn <63111101+arynyklas@users.noreply.github.com> Date: Sun, 24 Apr 2022 07:01:14 +0600 Subject: [PATCH 1/9] Fix datetime serialization error on Windows (#880) * fix #349 * fix #349 (2) --- aiogram/types/fields.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/aiogram/types/fields.py b/aiogram/types/fields.py index d7a1d8ca..a0adea65 100644 --- a/aiogram/types/fields.py +++ b/aiogram/types/fields.py @@ -1,6 +1,7 @@ import abc import datetime import weakref +import sys __all__ = ('BaseField', 'Field', 'ListField', 'DateTimeField', 'TextField', 'ListOfLists', 'ConstField') @@ -168,8 +169,13 @@ class DateTimeField(Field): out: datetime """ - def serialize(self, value: datetime.datetime): - return round(value.timestamp()) + if sys.platform == "win32": + def serialize(self, value: datetime.datetime): + return round((value - datetime.datetime(1970, 1, 1)).total_seconds()) + + else: + def serialize(self, value: datetime.datetime): + return round(value.timestamp()) def deserialize(self, value, parent=None): return datetime.datetime.fromtimestamp(value) From 8d58ed909db0d363c2b690455435181747b10de3 Mon Sep 17 00:00:00 2001 From: Alex Filiov <49647025+AlexDev-py@users.noreply.github.com> Date: Sat, 14 May 2022 23:01:32 +0900 Subject: [PATCH 2/9] fix Dispatcher.release_key (#908) del bucker['key'] raises KeyError: 'key' --- aiogram/dispatcher/dispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 7abe1d8d..22f6700c 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -1367,7 +1367,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin): bucket = await self.storage.get_bucket(chat=chat_id, user=user_id) if bucket and key in bucket: - del bucket['key'] + del bucket[key] await self.storage.set_bucket(chat=chat_id, user=user_id, bucket=bucket) return True return False From a0fb37a54de3232213a9a6e1182490a422834ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9?= Date: Thu, 2 Jun 2022 14:51:17 +0300 Subject: [PATCH 3/9] Add `protect_content` to webhook responses (#804) * fixed type hints of callback_data its impotant to remeber all data saved in callback_data is text even if you pass to it integer insofar as newbies often copy examples and modyfy this typing may help them make no mistake * add protect_content param to all supported webhook responses --- aiogram/dispatcher/webhook.py | 162 +++++++++++++++++++++++++++------- 1 file changed, 130 insertions(+), 32 deletions(-) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index 92e475ff..31ef88f1 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -448,6 +448,17 @@ class DisableWebPagePreviewMixin: return bot.disable_web_page_preview +class ProtectContentMixin: + def protect_content(self): + """ + Protect content + + :return: + """ + setattr(self, "protect_content", True) + return self + + class ParseModeMixin: def as_html(self): """ @@ -480,7 +491,9 @@ class ParseModeMixin: return bot.parse_mode -class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificationMixin, DisableWebPagePreviewMixin): +class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, + DisableNotificationMixin, DisableWebPagePreviewMixin, + ProtectContentMixin): """ You can send message with webhook by using this instance of this object. All arguments is equal with Bot.send_message method. @@ -488,7 +501,7 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio __slots__ = ('chat_id', 'text', 'parse_mode', 'disable_web_page_preview', 'disable_notification', - 'reply_to_message_id', 'reply_markup') + 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_MESSAGE @@ -497,6 +510,7 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio parse_mode: Optional[String] = None, disable_web_page_preview: Optional[Boolean] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -509,6 +523,8 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio :param disable_web_page_preview: Boolean (Optional) - Disables link previews for links in this message :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -526,6 +542,7 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio self.parse_mode = parse_mode self.disable_web_page_preview = disable_web_page_preview self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -536,6 +553,7 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio 'parse_mode': self.parse_mode, 'disable_web_page_preview': self.disable_web_page_preview, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } @@ -565,18 +583,19 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio return self -class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for forward messages of any kind on to webhook. """ - __slots__ = ('chat_id', 'from_chat_id', 'message_id', 'disable_notification') + __slots__ = ('chat_id', 'from_chat_id', 'message_id', 'disable_notification', 'protect_content') method = api.Methods.FORWARD_MESSAGE def __init__(self, chat_id: Union[Integer, String] = None, from_chat_id: Union[Integer, String] = None, message_id: Integer = None, - disable_notification: Optional[Boolean] = None): + disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None): """ :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username of the target channel (in the format @channelusername) @@ -584,12 +603,15 @@ class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin): message was sent (or channel username in the format @channelusername) :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param message_id: Integer - Message identifier in the chat specified in from_chat_id """ self.chat_id = chat_id self.from_chat_id = from_chat_id self.message_id = message_id self.disable_notification = disable_notification + self.protect_content = protect_content def message(self, message: types.Message): """ @@ -607,16 +629,18 @@ class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'chat_id': self.chat_id, 'from_chat_id': self.from_chat_id, 'message_id': self.message_id, - 'disable_notification': self.disable_notification + 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, } -class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send photo on to webhook. """ - __slots__ = ('chat_id', 'photo', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup') + __slots__ = ('chat_id', 'photo', 'caption', 'disable_notification', + 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_PHOTO @@ -624,6 +648,7 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin): photo: String, caption: Optional[String] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -637,6 +662,8 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin): 0-1024 characters after entities parsing :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -646,6 +673,7 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.photo = photo self.caption = caption self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -655,18 +683,20 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'photo': self.photo, 'caption': self.caption, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } -class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send audio on to webhook. """ __slots__ = ('chat_id', 'audio', 'caption', 'duration', 'performer', 'title', - 'disable_notification', 'reply_to_message_id', 'reply_markup') + 'disable_notification', 'protect_content', + 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_AUDIO @@ -677,6 +707,7 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin): performer: Optional[String] = None, title: Optional[String] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -693,6 +724,8 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin): :param title: String (Optional) - Track name :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -705,6 +738,7 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.performer = performer self.title = title self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -717,17 +751,19 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'performer': self.performer, 'title': self.title, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } -class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send document on to webhook. """ - __slots__ = ('chat_id', 'document', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup') + __slots__ = ('chat_id', 'document', 'caption', 'disable_notification', + 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_DOCUMENT @@ -735,6 +771,7 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin): document: String, caption: Optional[String] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -749,6 +786,8 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin): (may also be used when resending documents by file_id), 0-1024 characters after entities parsing :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -758,6 +797,7 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.document = document self.caption = caption self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -767,17 +807,19 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'document': self.document, 'caption': self.caption, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } -class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send video on to webhook. """ - __slots__ = ('chat_id', 'video', 'duration', 'width', 'height', 'caption', 'disable_notification', + __slots__ = ('chat_id', 'video', 'duration', 'width', 'height', 'caption', + 'disable_notification', 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_VIDEO @@ -789,6 +831,7 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin): height: Optional[Integer] = None, caption: Optional[String] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -806,6 +849,8 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin): 0-1024 characters after entities parsing :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -818,6 +863,7 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.height = height self.caption = caption self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -830,18 +876,19 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'height': self.height, 'caption': self.caption, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } -class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send voice on to webhook. """ __slots__ = ('chat_id', 'voice', 'caption', 'duration', 'disable_notification', - 'reply_to_message_id', 'reply_markup') + 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_VOICE @@ -850,6 +897,7 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): caption: Optional[String] = None, duration: Optional[Integer] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -864,6 +912,8 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): :param duration: Integer (Optional) - Duration of the voice message in seconds :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, @@ -874,6 +924,7 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.caption = caption self.duration = duration self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -884,18 +935,19 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'caption': self.caption, 'duration': self.duration, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } -class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send video note on to webhook. """ __slots__ = ('chat_id', 'video_note', 'duration', 'length', 'disable_notification', - 'reply_to_message_id', 'reply_markup') + 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_VIDEO_NOTE @@ -904,6 +956,7 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin): duration: Optional[Integer] = None, length: Optional[Integer] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -917,6 +970,8 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin): :param length: Integer (Optional) - Video width and height :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -927,6 +982,7 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.duration = duration self.length = length self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -937,23 +993,26 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'duration': self.duration, 'length': self.length, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } -class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use this method to send a group of photos or videos as an album. """ - __slots__ = ('chat_id', 'media', 'disable_notification', 'reply_to_message_id') + __slots__ = ('chat_id', 'media', 'disable_notification', + 'protect_content', 'reply_to_message_id') method = api.Methods.SEND_MEDIA_GROUP def __init__(self, chat_id: Union[Integer, String], media: Union[types.MediaGroup, List] = None, disable_notification: typing.Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: typing.Optional[Integer] = None): """ Use this method to send a group of photos or videos as an album. @@ -966,6 +1025,8 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin): :type media: :obj:`typing.Union[types.MediaGroup, typing.List]` :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :type disable_notification: :obj:`typing.Optional[base.Boolean]` + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :type reply_to_message_id: :obj:`typing.Optional[base.Integer]` :return: On success, an array of the sent Messages is returned. @@ -980,6 +1041,7 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.chat_id = chat_id self.media = media self.disable_notifications = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id def prepare(self): @@ -993,6 +1055,7 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'chat_id': self.chat_id, 'media': media, 'disable_notifications': self.disable_notifications, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id } @@ -1023,18 +1086,20 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin): return self -class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send location on to webhook. """ - __slots__ = ('chat_id', 'latitude', 'longitude', 'disable_notification', 'reply_to_message_id', 'reply_markup') + __slots__ = ('chat_id', 'latitude', 'longitude', 'disable_notification', + 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_LOCATION def __init__(self, chat_id: Union[Integer, String], latitude: Float, longitude: Float, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -1045,6 +1110,8 @@ class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin): :param longitude: Float - Longitude of location :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -1054,6 +1121,7 @@ class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.latitude = latitude self.longitude = longitude self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -1063,18 +1131,21 @@ class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'latitude': self.latitude, 'longitude': self.longitude, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } -class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send venue on to webhook. """ - __slots__ = ('chat_id', 'latitude', 'longitude', 'title', 'address', 'foursquare_id', - 'disable_notification', 'reply_to_message_id', 'reply_markup') + __slots__ = ('chat_id', 'latitude', 'longitude', 'title', + 'address', 'foursquare_id', + 'disable_notification', 'protect_content', + 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_VENUE @@ -1085,6 +1156,7 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin): address: String, foursquare_id: Optional[String] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -1098,6 +1170,8 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin): :param foursquare_id: String (Optional) - Foursquare identifier of the venue :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -1110,6 +1184,7 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.address = address self.foursquare_id = foursquare_id self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -1122,18 +1197,19 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'address': self.address, 'foursquare_id': self.foursquare_id, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } -class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send contact on to webhook. """ __slots__ = ('chat_id', 'phone_number', 'first_name', 'last_name', 'disable_notification', - 'reply_to_message_id', 'reply_markup') + 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_CONTACT @@ -1142,6 +1218,7 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin): first_name: String, last_name: Optional[String] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[Union[ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None): @@ -1153,6 +1230,8 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin): :param last_name: String (Optional) - Contact's last name :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -1163,6 +1242,7 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.first_name = first_name self.last_name = last_name self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -1173,6 +1253,7 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'first_name': self.first_name, 'last_name': self.last_name, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } @@ -1730,18 +1811,20 @@ class DeleteMessage(BaseResponse): } -class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send sticker on to webhook. """ - __slots__ = ('chat_id', 'sticker', 'disable_notification', 'reply_to_message_id', 'reply_markup') + __slots__ = ('chat_id', 'sticker', 'disable_notification', 'protect_content', + 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_STICKER def __init__(self, chat_id: Union[Integer, String], sticker: String, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[ Union[types.InlineKeyboardMarkup, @@ -1755,6 +1838,8 @@ class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin): or upload a new one using multipart/form-data. More info on Sending Files » :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - Additional interface options. A JSON-serialized object for an inline keyboard, @@ -1763,6 +1848,7 @@ class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.chat_id = chat_id self.sticker = sticker self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -1771,6 +1857,7 @@ class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'chat_id': self.chat_id, 'sticker': self.sticker, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } @@ -1985,7 +2072,7 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'currency', 'prices', 'photo_url', 'photo_size', 'photo_width', 'photo_height', 'need_name', 'need_phone_number', 'need_email', 'need_shipping_address', 'send_phone_number_to_provider', 'send_email_to_provider', 'is_flexible', - 'disable_notification', 'reply_to_message_id', 'reply_markup') + 'disable_notification', 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_INVOICE @@ -2009,6 +2096,7 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): send_email_to_provider: Optional[Boolean] = None, is_flexible: Optional[Boolean] = None, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[types.InlineKeyboardMarkup] = None): """ @@ -2042,6 +2130,8 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): :param is_flexible: Boolean (Optional) - Pass True, if the final price depends on the shipping method :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button. @@ -2066,6 +2156,7 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.send_email_to_provider = send_email_to_provider self.is_flexible = is_flexible self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -2091,6 +2182,7 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'send_email_to_provider': self.send_email_to_provider, 'is_flexible': self.is_flexible, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } @@ -2168,18 +2260,20 @@ class AnswerPreCheckoutQuery(BaseResponse): } -class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin): +class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin, ProtectContentMixin): """ Use that response type for send game on to webhook. """ - __slots__ = ('chat_id', 'game_short_name', 'disable_notification', 'reply_to_message_id', 'reply_markup') + __slots__ = ('chat_id', 'game_short_name', 'disable_notification', + 'protect_content', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_GAME def __init__(self, chat_id: Integer, game_short_name: String, disable_notification: Optional[Boolean] = None, + protect_content: Optional[Boolean] = None, reply_to_message_id: Optional[Integer] = None, reply_markup: Optional[types.InlineKeyboardMarkup] = None): """ @@ -2188,6 +2282,8 @@ class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin): Set up your games via Botfather. :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a notification with no sound. + :param protect_content: Boolean (Optional) - Protects the contents of sent messages + from forwarding and saving :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. If empty, one ‘Play game_title’ button will be shown. If not empty, the first button must launch the game. @@ -2195,6 +2291,7 @@ class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin): self.chat_id = chat_id self.game_short_name = game_short_name self.disable_notification = disable_notification + self.protect_content = protect_content self.reply_to_message_id = reply_to_message_id self.reply_markup = reply_markup @@ -2203,6 +2300,7 @@ class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin): 'chat_id': self.chat_id, 'game_short_name': self.game_short_name, 'disable_notification': self.disable_notification, + 'protect_content': self.protect_content, 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup), } From 7940613ec078decf79762e1a1e9a4f2c4a1c2998 Mon Sep 17 00:00:00 2001 From: Andrew <11490628+andrew000@users.noreply.github.com> Date: Tue, 21 Jun 2022 03:26:17 +0300 Subject: [PATCH 4/9] Rework warning for not defined fields (#931) --- aiogram/types/base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/aiogram/types/base.py b/aiogram/types/base.py index cca3d5f1..b5befd38 100644 --- a/aiogram/types/base.py +++ b/aiogram/types/base.py @@ -3,12 +3,14 @@ from __future__ import annotations import io import logging import typing +import warnings from typing import TypeVar from babel.support import LazyProxy from .fields import BaseField from ..utils import json +from ..utils.exceptions import AIOGramWarning from ..utils.mixins import ContextInstanceMixin if typing.TYPE_CHECKING: from ..bot.bot import Bot @@ -243,8 +245,10 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject): return self.props[key].set_value(self, value, self.conf.get('parent', self)) self.values[key] = value - # Log warning when Telegram silently adds new Fields - log.warning("Field '%s' doesn't exist in %s", key, self.__class__) + # Show warning when Telegram silently adds new Fields + warnings.warn(f"Bot API Field {key!r} is not defined in {self.__class__!r} class.\n" + "Bot API has been updated. Check for updates at https://telegram.org/blog and " + "https://github.com/aiogram/aiogram/releases", AIOGramWarning) def __contains__(self, item: str) -> bool: """ From 3b66a2b2e0a90ffc80582be3d1ef0e7045ce51fa Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 22 Jun 2022 00:59:49 +0300 Subject: [PATCH 5/9] Added full support of Telegram Bot API 6.1; Ported web-app utils; Deprecated emoji module; --- aiogram/bot/api.py | 1 + aiogram/bot/bot.py | 68 ++++++++++++++++++++++++++++++++++++++++ aiogram/types/chat.py | 2 ++ aiogram/types/sticker.py | 3 +- aiogram/types/user.py | 2 ++ aiogram/utils/emoji.py | 8 +++++ aiogram/utils/web_app.py | 67 +++++++++++++++++++++++++++++++++++++++ message.py | 17 ++++++++++ 8 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 aiogram/utils/web_app.py create mode 100644 message.py diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index f2cc7234..509e6c71 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -287,6 +287,7 @@ class Methods(Helper): # Payments SEND_INVOICE = Item() # sendInvoice + CREATE_INVOICE_LINK = Item() # createInvoiceLink ANSWER_SHIPPING_QUERY = Item() # answerShippingQuery ANSWER_PRE_CHECKOUT_QUERY = Item() # answerPreCheckoutQuery diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index 461f68b2..c883409f 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -117,6 +117,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): max_connections: typing.Optional[base.Integer] = None, allowed_updates: typing.Optional[typing.List[base.String]] = None, drop_pending_updates: typing.Optional[base.Boolean] = None, + secret_token: typing.Optional[str] = None, ) -> base.Boolean: """ Use this method to specify a url and receive incoming updates via an outgoing @@ -165,6 +166,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): :param drop_pending_updates: Pass True to drop all pending updates :type drop_pending_updates: :obj:`typing.Optional[base.Boolean]` + :param secret_token: A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” + in every webhook request, 1-256 characters. Only characters A-Z, a-z, 0-9, _ and - are allowed. + The header is useful to ensure that the request comes from a webhook set by you. + :type secret_token: :obj:`typing.Optional[str]` :return: Returns true :rtype: :obj:`base.Boolean` """ @@ -3397,6 +3402,69 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): result = await self.request(api.Methods.SEND_INVOICE, payload_) return types.Message(**result) + async def create_invoice_link(self, + title: base.String, + description: base.String, + payload: base.String, + provider_token: base.String, + currency: base.String, + prices: typing.List[types.LabeledPrice], + max_tip_amount: typing.Optional[int] = None, + suggested_tip_amounts: typing.Optional[typing.List[int]] = None, + provider_data: typing.Optional[base.String] = None, + photo_url: typing.Optional[str] = None, + photo_size: typing.Optional[int] = None, + photo_width: typing.Optional[int] = None, + photo_height: typing.Optional[int] = None, + need_name: typing.Optional[bool] = None, + need_phone_number: typing.Optional[bool] = None, + need_email: typing.Optional[bool] = None, + need_shipping_address: typing.Optional[bool] = None, + send_phone_number_to_provider: typing.Optional[bool] = None, + send_email_to_provider: typing.Optional[bool] = None, + is_flexible: typing.Optional[bool] = None, + ) -> str: + """ + Use this method to create a link for an invoice. On success, the created link is returned. + + Source: https://core.telegram.org/bots/api#createinvoicelink + + :param title: Product name, 1-32 characters + :param description: Product description, 1-255 characters + :param payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes. + :param provider_token: Payment provider token, obtained via BotFather + :param currency: Three-letter ISO 4217 currency code, see more on currencies + :param prices: Price breakdown, a JSON-serialized list of components + (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) + :param max_tip_amount: The maximum accepted amount for tips in the smallest units of the currency + (integer, not float/double). For example, for a maximum tip of US$ 1.45 pass max_tip_amount = 145. + See the exp parameter in currencies.json, it shows the number of digits past the decimal point for + each currency (2 for the majority of currencies). Defaults to 0 + :param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the smallest units + of the currency (integer, not float/double). At most 4 suggested tip amounts can be specified. + The suggested tip amounts must be positive, passed in a strictly increased order and must not + exceed max_tip_amount. + :param provider_data: JSON-serialized data about the invoice, which will be shared with the payment provider. + A detailed description of required fields should be provided by the payment provider. + :param photo_url: URL of the product photo for the invoice. + Can be a photo of the goods or a marketing image for a service. + :param photo_size: Photo size in bytes + :param photo_width: Photo width + :param photo_height: Photo height + :param need_name: Pass True, if you require the user's full name to complete the order + :param need_phone_number: Pass True, if you require the user's phone number to complete the order + :param need_email: Pass True, if you require the user's email address to complete the order + :param need_shipping_address: Pass True, if you require the user's shipping address to complete the order + :param send_phone_number_to_provider: Pass True, if the user's phone number should be sent to the provider + :param send_email_to_provider: Pass True, if the user's email address should be sent to the provider + :param is_flexible: Pass True, if the final price depends on the shipping method + :return: + """ + prices = prepare_arg([price.to_python() if hasattr(price, 'to_python') else price for price in prices]) + payload = generate_payload(**locals()) + + return await self.request(api.Methods.CREATE_INVOICE_LINK, payload) + async def answer_shipping_query(self, shipping_query_id: base.String, ok: base.Boolean, shipping_options: typing.Union[typing.List[types.ShippingOption], None] = None, error_message: typing.Optional[base.String] = None) -> base.Boolean: diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index c18ad88b..dd11100a 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -31,6 +31,8 @@ class Chat(base.TelegramObject): photo: ChatPhoto = fields.Field(base=ChatPhoto) bio: base.String = fields.Field() has_private_forwards: base.Boolean = fields.Field() + join_to_send_messages: base.Boolean = fields.Field() + join_by_request: base.Boolean = fields.Field() description: base.String = fields.Field() invite_link: base.String = fields.Field() pinned_message: 'Message' = fields.Field(base='Message') diff --git a/aiogram/types/sticker.py b/aiogram/types/sticker.py index 20c162e8..f1c0f527 100644 --- a/aiogram/types/sticker.py +++ b/aiogram/types/sticker.py @@ -3,7 +3,7 @@ from . import fields from . import mixins from .mask_position import MaskPosition from .photo_size import PhotoSize - +from .file import File class Sticker(base.TelegramObject, mixins.Downloadable): """ @@ -20,6 +20,7 @@ class Sticker(base.TelegramObject, mixins.Downloadable): thumb: PhotoSize = fields.Field(base=PhotoSize) emoji: base.String = fields.Field() set_name: base.String = fields.Field() + premium_animation: File = fields.Field(base=File) mask_position: MaskPosition = fields.Field(base=MaskPosition) file_size: base.Integer = fields.Field() diff --git a/aiogram/types/user.py b/aiogram/types/user.py index 6bde2dcd..3d594ecc 100644 --- a/aiogram/types/user.py +++ b/aiogram/types/user.py @@ -22,6 +22,8 @@ class User(base.TelegramObject): last_name: base.String = fields.Field() username: base.String = fields.Field() language_code: base.String = fields.Field() + is_premium: base.Boolean = fields.Field() + added_to_attachment_menu: base.Boolean = fields.Field() can_join_groups: base.Boolean = fields.Field() can_read_all_group_messages: base.Boolean = fields.Field() supports_inline_queries: base.Boolean = fields.Field() diff --git a/aiogram/utils/emoji.py b/aiogram/utils/emoji.py index 07faff56..92252076 100644 --- a/aiogram/utils/emoji.py +++ b/aiogram/utils/emoji.py @@ -1,3 +1,5 @@ +import warnings + try: import emoji except ImportError: @@ -5,8 +7,14 @@ except ImportError: def emojize(text): + warnings.warn(message="'aiogram.utils.emoji' module deprecated, use emoji symbols directly instead," + "this function will be removed in aiogram v2.22", + category=DeprecationWarning, stacklevel=2) return emoji.emojize(text, use_aliases=True) def demojize(text): + warnings.warn(message="'aiogram.utils.emoji' module deprecated, use emoji symbols directly instead," + "this function will be removed in aiogram v2.22", + category=DeprecationWarning, stacklevel=2) return emoji.demojize(text) diff --git a/aiogram/utils/web_app.py b/aiogram/utils/web_app.py new file mode 100644 index 00000000..1ba43658 --- /dev/null +++ b/aiogram/utils/web_app.py @@ -0,0 +1,67 @@ +import hashlib +import hmac +from operator import itemgetter +from typing import Callable, Any, Dict +from urllib.parse import parse_qsl + + +def check_webapp_signature(token: str, init_data: str) -> bool: + """ + Check incoming WebApp init data signature + + Source: https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app + + :param token: + :param init_data: + :return: + """ + try: + parsed_data = dict(parse_qsl(init_data)) + except ValueError: + # Init data is not a valid query string + return False + if "hash" not in parsed_data: + # Hash is not present in init data + return False + + hash_ = parsed_data.pop('hash') + data_check_string = "\n".join( + f"{k}={v}" for k, v in sorted(parsed_data.items(), key=itemgetter(0)) + ) + secret_key = hmac.new( + key=b"WebAppData", msg=token.encode(), digestmod=hashlib.sha256 + ) + calculated_hash = hmac.new( + key=secret_key.digest(), msg=data_check_string.encode(), digestmod=hashlib.sha256 + ).hexdigest() + return calculated_hash == hash_ + + +def parse_init_data(init_data: str, _loads: Callable[..., Any]) -> Dict[str, Any]: + """ + Parse WebApp init data and return it as dict + + :param init_data: + :param _loads: + :return: + """ + result = {} + for key, value in parse_qsl(init_data): + if (value.startswith('[') and value.endswith(']')) or (value.startswith('{') and value.endswith('}')): + value = _loads(value) + result[key] = value + return result + + +def safe_parse_webapp_init_data(token: str, init_data: str, _loads: Callable[..., Any]) -> Dict[str, Any]: + """ + Validate WebApp init data and return it as dict + + :param token: + :param init_data: + :param _loads: + :return: + """ + if check_webapp_signature(token, init_data): + return parse_init_data(init_data, _loads) + raise ValueError("Invalid init data signature") diff --git a/message.py b/message.py new file mode 100644 index 00000000..ef790190 --- /dev/null +++ b/message.py @@ -0,0 +1,17 @@ +import asyncio +import secrets + +from aiogram import Bot + + +async def main(): + bot = Bot(token="5115369270:AAFlipWd1qbhc7cIe0nRM-SyGLkTC_9Ulgg") + index = 0 + while True: + index += 1 + print(index) + await bot.send_message(chat_id=879238251, text=secrets.token_urlsafe(24)) + await asyncio.sleep(.2) + + +asyncio.run(main()) From ce5077876bd29ed08959bf9d503abdbc06024865 Mon Sep 17 00:00:00 2001 From: ZharkovMihail <32340066+ZharkovMihail@users.noreply.github.com> Date: Wed, 22 Jun 2022 01:02:23 +0300 Subject: [PATCH 6/9] fix unregister handlers (#932) --- aiogram/dispatcher/handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiogram/dispatcher/handler.py b/aiogram/dispatcher/handler.py index 10a94924..85d7731d 100644 --- a/aiogram/dispatcher/handler.py +++ b/aiogram/dispatcher/handler.py @@ -76,8 +76,8 @@ class Handler: """ for handler_obj in self.handlers: registered = handler_obj.handler - if handler is registered: - self.handlers.remove(handler_obj) + if handler in self.handlers: + self.handlers.remove(handler) return True raise ValueError('This handler is not registered!') From 1ced320a195e022b5ba46f4011dd57cc621b244b Mon Sep 17 00:00:00 2001 From: Abror Maksudov <93431820+abrikk@users.noreply.github.com> Date: Wed, 22 Jun 2022 03:03:44 +0500 Subject: [PATCH 7/9] make ChatTypeFilter work with inline queries (#915) * make ChatTypeFilter work with inline queries * add condition when call.message is none --- aiogram/dispatcher/filters/builtin.py | 12 +++++++----- aiogram/types/chat.py | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index ebd38f08..373dafe5 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -728,18 +728,20 @@ class ChatTypeFilter(BoundFilter): self.chat_type: typing.Set[str] = set(chat_type) - async def check(self, obj: Union[Message, CallbackQuery, ChatMemberUpdated]): + async def check(self, obj: Union[Message, CallbackQuery, ChatMemberUpdated, InlineQuery]): if isinstance(obj, Message): - obj = obj.chat + chat_type = obj.chat.type elif isinstance(obj, CallbackQuery): - obj = obj.message.chat + chat_type = obj.message.chat.type if obj.message else None elif isinstance(obj, ChatMemberUpdated): - obj = obj.chat + chat_type = obj.chat.type + elif isinstance(obj, InlineQuery): + chat_type = obj.chat_type else: warnings.warn("ChatTypeFilter doesn't support %s as input", type(obj)) return False - return obj.type in self.chat_type + return chat_type in self.chat_type class MediaGroupFilter(BoundFilter): diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index dd11100a..4377851a 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -654,6 +654,7 @@ class ChatType(helper.Helper): """ List of chat types + :key: SENDER :key: PRIVATE :key: GROUP :key: SUPER_GROUP @@ -663,6 +664,7 @@ class ChatType(helper.Helper): mode = helper.HelperMode.lowercase + SENDER = helper.Item() # sender PRIVATE = helper.Item() # private GROUP = helper.Item() # group SUPERGROUP = helper.Item() # supergroup From c289da35c331403e1fef0fb4fbaab726b5c86a9a Mon Sep 17 00:00:00 2001 From: ENCRYPTED <48645524+ENCRYPTEDFOREVER@users.noreply.github.com> Date: Wed, 22 Jun 2022 01:04:47 +0300 Subject: [PATCH 8/9] Added from_id property to Message (#899) --- aiogram/types/message.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 5edb4a3e..57dcff44 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -270,6 +270,15 @@ class Message(base.TelegramObject): return text_decorator.unparse(text, entities) + @property + def from_id(self) -> int: + """ + User id if sent by user or chat/channel id if sent on behalf of a channel or chat + + :return: int + """ + return self.sender_chat.id if self.sender_chat else self.from_user.id + @property def md_text(self) -> str: """ From cdb6e2b7dffd042c3b8a62bec9bef83ece3b3893 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 22 Jun 2022 01:08:53 +0300 Subject: [PATCH 9/9] Bump version --- README.rst | 2 +- aiogram/__init__.py | 4 ++-- docs/source/index.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 1b521252..fd95af67 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ AIOGramBot :target: https://pypi.python.org/pypi/aiogram :alt: Supported python versions -.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.0-blue.svg?style=flat-square&logo=telegram +.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.1-blue.svg?style=flat-square&logo=telegram :target: https://core.telegram.org/bots/api :alt: Telegram Bot API diff --git a/aiogram/__init__.py b/aiogram/__init__.py index e3144b31..85725de0 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -43,5 +43,5 @@ __all__ = ( 'utils', ) -__version__ = '2.20' -__api_version__ = '6.0' +__version__ = '2.21' +__api_version__ = '6.1' diff --git a/docs/source/index.rst b/docs/source/index.rst index 82505aba..84ce6405 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -22,7 +22,7 @@ Welcome to aiogram's documentation! :target: https://pypi.python.org/pypi/aiogram :alt: Supported python versions - .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.0-blue.svg?style=flat-square&logo=telegram + .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.1-blue.svg?style=flat-square&logo=telegram :target: https://core.telegram.org/bots/api :alt: Telegram Bot API