From ad4b215e7140df8b0cb6cfc4b8f0615f3f266de2 Mon Sep 17 00:00:00 2001 From: Mykola Solodukha Date: Tue, 12 Jan 2021 21:21:56 +0200 Subject: [PATCH 01/19] Add ability to `.answer_*` and `.reply_*` live location --- aiogram/types/message.py | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 4b5a47bc..dd356085 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -957,7 +957,10 @@ class Message(base.TelegramObject): self, latitude: base.Float, longitude: base.Float, + horizontal_accuracy: typing.Optional[base.Float] = None, live_period: typing.Optional[base.Integer] = None, + heading: typing.Optional[base.Integer] = None, + proximity_alert_radius: typing.Optional[base.Integer] = None, disable_notification: typing.Optional[base.Boolean] = None, allow_sending_without_reply: typing.Optional[base.Boolean] = None, reply_markup: typing.Union[ @@ -980,9 +983,22 @@ class Message(base.TelegramObject): :param longitude: Longitude of the location :type longitude: :obj:`base.Float` + :param horizontal_accuracy: The radius of uncertainty for the location, + measured in meters; 0-1500 + :type horizontal_accuracy: :obj:`typing.Optional[base.Float]` + :param live_period: Period in seconds for which the location will be updated :type live_period: :obj:`typing.Optional[base.Integer]` + :param heading: For live locations, a direction in which the user is moving, + in degrees. Must be between 1 and 360 if specified. + :type heading: :obj:`typing.Optional[base.Integer]` + + :param proximity_alert_radius: For live locations, a maximum distance for + proximity alerts about approaching another chat member, in meters. Must + be between 1 and 100000 if specified. + :type proximity_alert_radius: :obj:`typing.Optional[base.Integer]` + :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :type disable_notification: :obj:`typing.Optional[base.Boolean]` @@ -1005,7 +1021,10 @@ class Message(base.TelegramObject): chat_id=self.chat.id, latitude=latitude, longitude=longitude, + horizontal_accuracy=horizontal_accuracy, live_period=live_period, + heading=heading, + proximity_alert_radius=proximity_alert_radius, disable_notification=disable_notification, reply_to_message_id=self.message_id if reply else None, allow_sending_without_reply=allow_sending_without_reply, @@ -2055,7 +2074,10 @@ class Message(base.TelegramObject): self, latitude: base.Float, longitude: base.Float, + horizontal_accuracy: typing.Optional[base.Float] = None, live_period: typing.Optional[base.Integer] = None, + heading: typing.Optional[base.Integer] = None, + proximity_alert_radius: typing.Optional[base.Integer] = None, disable_notification: typing.Optional[base.Boolean] = None, reply_markup: typing.Union[ InlineKeyboardMarkup, @@ -2073,18 +2095,37 @@ class Message(base.TelegramObject): :param latitude: Latitude of the location :type latitude: :obj:`base.Float` + :param longitude: Longitude of the location :type longitude: :obj:`base.Float` + + :param horizontal_accuracy: The radius of uncertainty for the location, + measured in meters; 0-1500 + :type horizontal_accuracy: :obj:`typing.Optional[base.Float]` + :param live_period: Period in seconds for which the location will be updated :type live_period: :obj:`typing.Optional[base.Integer]` + + :param heading: For live locations, a direction in which the user is moving, + in degrees. Must be between 1 and 360 if specified. + :type heading: :obj:`typing.Optional[base.Integer]` + + :param proximity_alert_radius: For live locations, a maximum distance for + proximity alerts about approaching another chat member, in meters. Must + be between 1 and 100000 if specified. + :type proximity_alert_radius: :obj:`typing.Optional[base.Integer]` + :param disable_notification: Sends the message silently. Users will receive a notification with no sound. :type disable_notification: :obj:`typing.Optional[base.Boolean]` + :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` + :param reply: fill 'reply_to_message_id' :type reply: :obj:`base.Boolean` + :return: On success, the sent Message is returned. :rtype: :obj:`types.Message` """ @@ -2092,7 +2133,10 @@ class Message(base.TelegramObject): chat_id=self.chat.id, latitude=latitude, longitude=longitude, + horizontal_accuracy=horizontal_accuracy, live_period=live_period, + heading=heading, + proximity_alert_radius=proximity_alert_radius, disable_notification=disable_notification, reply_to_message_id=self.message_id if reply else None, reply_markup=reply_markup, From 8656c6bc241a1a3e255fa6e37e14ebbe9deea519 Mon Sep 17 00:00:00 2001 From: Mykola Solodukha Date: Fri, 15 Jan 2021 05:12:32 +0200 Subject: [PATCH 02/19] Fix `args` order in `.reply_*` and `.answer_location()` --- aiogram/types/message.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index dd356085..ee6cd7c2 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -957,10 +957,7 @@ class Message(base.TelegramObject): self, latitude: base.Float, longitude: base.Float, - horizontal_accuracy: typing.Optional[base.Float] = None, live_period: typing.Optional[base.Integer] = None, - heading: typing.Optional[base.Integer] = None, - proximity_alert_radius: typing.Optional[base.Integer] = None, disable_notification: typing.Optional[base.Boolean] = None, allow_sending_without_reply: typing.Optional[base.Boolean] = None, reply_markup: typing.Union[ @@ -971,6 +968,9 @@ class Message(base.TelegramObject): None, ] = None, reply: base.Boolean = False, + horizontal_accuracy: typing.Optional[base.Float] = None, + heading: typing.Optional[base.Integer] = None, + proximity_alert_radius: typing.Optional[base.Integer] = None, ) -> Message: """ Use this method to send point on the map. @@ -2074,10 +2074,7 @@ class Message(base.TelegramObject): self, latitude: base.Float, longitude: base.Float, - horizontal_accuracy: typing.Optional[base.Float] = None, live_period: typing.Optional[base.Integer] = None, - heading: typing.Optional[base.Integer] = None, - proximity_alert_radius: typing.Optional[base.Integer] = None, disable_notification: typing.Optional[base.Boolean] = None, reply_markup: typing.Union[ InlineKeyboardMarkup, @@ -2087,6 +2084,9 @@ class Message(base.TelegramObject): None, ] = None, reply: base.Boolean = True, + horizontal_accuracy: typing.Optional[base.Float] = None, + heading: typing.Optional[base.Integer] = None, + proximity_alert_radius: typing.Optional[base.Integer] = None, ) -> Message: """ Use this method to send point on the map. From adba85c4bea392ea059d31d888b90518c5f030c8 Mon Sep 17 00:00:00 2001 From: nacknime <34421603+nacknime-official@users.noreply.github.com> Date: Fri, 22 Jan 2021 09:49:46 +0200 Subject: [PATCH 03/19] docs(install): typo "form" -> "from" (#498) --- docs/source/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index 74c12865..4eaa351f 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -32,7 +32,7 @@ From sources $ cd aiogram $ python setup.py install - Or if you want to install stable version (The same with version form PyPi): + Or if you want to install stable version (The same with version from PyPi): .. code-block:: bash From cd5a77211008ccbf94e12248a1a6b31bc3926811 Mon Sep 17 00:00:00 2001 From: Mykola Solodukha Date: Sat, 23 Jan 2021 19:04:30 +0200 Subject: [PATCH 04/19] Logically order args --- aiogram/types/message.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index ee6cd7c2..b018ea11 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -960,6 +960,9 @@ class Message(base.TelegramObject): live_period: typing.Optional[base.Integer] = None, disable_notification: typing.Optional[base.Boolean] = None, allow_sending_without_reply: typing.Optional[base.Boolean] = None, + horizontal_accuracy: typing.Optional[base.Float] = None, + heading: typing.Optional[base.Integer] = None, + proximity_alert_radius: typing.Optional[base.Integer] = None, reply_markup: typing.Union[ InlineKeyboardMarkup, ReplyKeyboardMarkup, @@ -968,9 +971,6 @@ class Message(base.TelegramObject): None, ] = None, reply: base.Boolean = False, - horizontal_accuracy: typing.Optional[base.Float] = None, - heading: typing.Optional[base.Integer] = None, - proximity_alert_radius: typing.Optional[base.Integer] = None, ) -> Message: """ Use this method to send point on the map. @@ -2076,6 +2076,9 @@ class Message(base.TelegramObject): longitude: base.Float, live_period: typing.Optional[base.Integer] = None, disable_notification: typing.Optional[base.Boolean] = None, + horizontal_accuracy: typing.Optional[base.Float] = None, + heading: typing.Optional[base.Integer] = None, + proximity_alert_radius: typing.Optional[base.Integer] = None, reply_markup: typing.Union[ InlineKeyboardMarkup, ReplyKeyboardMarkup, @@ -2084,9 +2087,6 @@ class Message(base.TelegramObject): None, ] = None, reply: base.Boolean = True, - horizontal_accuracy: typing.Optional[base.Float] = None, - heading: typing.Optional[base.Integer] = None, - proximity_alert_radius: typing.Optional[base.Integer] = None, ) -> Message: """ Use this method to send point on the map. From 4701b852bde1f0541c303e93fea8a3f6c1524a0b Mon Sep 17 00:00:00 2001 From: Yan Khachko Date: Tue, 9 Feb 2021 11:38:51 +0000 Subject: [PATCH 05/19] Update chat_type_filter.py --- examples/chat_type_filter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/chat_type_filter.py b/examples/chat_type_filter.py index 08bb1858..1224e3f1 100644 --- a/examples/chat_type_filter.py +++ b/examples/chat_type_filter.py @@ -22,8 +22,8 @@ dp = Dispatcher(bot) @dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.CHANNEL]) async def send_welcome(message: types.Message): """ - This handler will be called when user sends `/start` or `/help` command - """ + This handler will be called when user sends message in private chat or channel + """ await message.reply("Hi!\nI'm hearing your messages in private chats and channels") # propagate message to the next handler @@ -33,7 +33,7 @@ async def send_welcome(message: types.Message): @dp.message_handler(chat_type=ChatType.PRIVATE) async def send_welcome(message: types.Message): """ - This handler will be called when user sends `/start` or `/help` command + This handler will be called when user sends message in private chat """ await message.reply("Hi!\nI'm hearing your messages only in private chats") From c625b7beb06c6695b78aea4d081c9a2ee4852bf2 Mon Sep 17 00:00:00 2001 From: Yan Khachko Date: Tue, 9 Feb 2021 15:31:46 +0000 Subject: [PATCH 06/19] Update chat_type_filter.py --- examples/chat_type_filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/chat_type_filter.py b/examples/chat_type_filter.py index 1224e3f1..e57f8825 100644 --- a/examples/chat_type_filter.py +++ b/examples/chat_type_filter.py @@ -22,8 +22,8 @@ dp = Dispatcher(bot) @dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.CHANNEL]) async def send_welcome(message: types.Message): """ - This handler will be called when user sends message in private chat or channel - """ + This handler will be called when user sends message in private chat or channel + """ await message.reply("Hi!\nI'm hearing your messages in private chats and channels") # propagate message to the next handler From 75222b8af020506b61af5227af2a51c7cd32d190 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 7 Mar 2021 15:27:41 +0200 Subject: [PATCH 07/19] #516: Fix updating chat --- aiogram/__init__.py | 2 +- aiogram/types/base.py | 3 ++- aiogram/types/chat.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/aiogram/__init__.py b/aiogram/__init__.py index a266ce1b..b04113ca 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -43,5 +43,5 @@ __all__ = ( 'utils', ) -__version__ = '2.11.2' +__version__ = '2.11.3' __api_version__ = '5.0' diff --git a/aiogram/types/base.py b/aiogram/types/base.py index e64d3398..373f47be 100644 --- a/aiogram/types/base.py +++ b/aiogram/types/base.py @@ -212,7 +212,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject): """ if item in self.props: return self.props[item].get_value(self) - raise KeyError(item) + return self.values[item] def __setitem__(self, key: str, value: typing.Any) -> None: """ @@ -224,6 +224,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject): """ if key in self.props: return self.props[key].set_value(self, value, self.conf.get('parent', None)) + self.values[key] = value raise KeyError(key) def __contains__(self, item: str) -> bool: diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 1ff5dc75..4f062b49 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -113,7 +113,7 @@ class Chat(base.TelegramObject): async def update_chat(self): """ - User this method to update Chat data + Use this method to update Chat data :return: None """ From 24fb07d3fec290ae4bec37553e82e8ae15c901fb Mon Sep 17 00:00:00 2001 From: Oleg A Date: Sun, 14 Mar 2021 19:05:17 +0300 Subject: [PATCH 08/19] Bot API 5.1 (#519) * version update * added ChatMemberUpdated class * added ChatInviteLink class * 2.x version update * update types added * added methods createChatInviteLink, editChatInviteLink, revokeChatInviteLink * Voice Chat types added * added Message fields: voice_chat_started, voice_chat_ended, voice_chat_participants_invited * can_manage_voice_chats added * chat links shortcuts added * bowling dice support * reordered ChatMembers params (no changes) * Added can_manage_chat to the class ChatMember and parameter can_manage_chat to the method promoteChatMember * kick_chat_member refactored + docs update * Added the parameter revoke_messages to the method kickChatMember * updated kick_chat_member shortcut for Chat * Added the type MessageAutoDeleteTimerChanged and the field message_auto_delete_timer_changed to the class Message * feat: add methods to register my_chat_member and chat_member handlers * Updated filters for new event types Co-authored-by: Alex Root Junior --- aiogram/__init__.py | 4 +- aiogram/bot/api.py | 5 +- aiogram/bot/bot.py | 145 ++++++++++++++++-- aiogram/contrib/middlewares/logging.py | 20 +++ aiogram/dispatcher/dispatcher.py | 127 +++++++++++++++ aiogram/dispatcher/filters/builtin.py | 23 ++- aiogram/types/__init__.py | 12 ++ aiogram/types/chat.py | 81 ++++++++-- aiogram/types/chat_invite_link.py | 20 +++ aiogram/types/chat_member.py | 10 +- aiogram/types/chat_member_updated.py | 22 +++ aiogram/types/dice.py | 5 +- aiogram/types/message.py | 28 +++- .../message_auto_delete_timer_changed.py | 11 ++ aiogram/types/update.py | 5 + aiogram/types/voice_chat_ended.py | 13 ++ .../types/voice_chat_participants_invited.py | 16 ++ aiogram/types/voice_chat_started.py | 12 ++ 18 files changed, 510 insertions(+), 49 deletions(-) create mode 100644 aiogram/types/chat_invite_link.py create mode 100644 aiogram/types/chat_member_updated.py create mode 100644 aiogram/types/message_auto_delete_timer_changed.py create mode 100644 aiogram/types/voice_chat_ended.py create mode 100644 aiogram/types/voice_chat_participants_invited.py create mode 100644 aiogram/types/voice_chat_started.py diff --git a/aiogram/__init__.py b/aiogram/__init__.py index b04113ca..ef832be9 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -43,5 +43,5 @@ __all__ = ( 'utils', ) -__version__ = '2.11.3' -__api_version__ = '5.0' +__version__ = '2.12' +__api_version__ = '5.1' diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index 9b86e7ca..e3d3bf9a 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -189,7 +189,7 @@ class Methods(Helper): """ Helper for Telegram API Methods listed on https://core.telegram.org/bots/api - List is updated to Bot API 5.0 + List is updated to Bot API 5.1 """ mode = HelperMode.lowerCamelCase @@ -231,6 +231,9 @@ class Methods(Helper): SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE = Item() # setChatAdministratorCustomTitle SET_CHAT_PERMISSIONS = Item() # setChatPermissions EXPORT_CHAT_INVITE_LINK = Item() # exportChatInviteLink + CREATE_CHAT_INVITE_LINK = Item() # createChatInviteLink + EDIT_CHAT_INVITE_LINK = Item() # editChatInviteLink + REVOKE_CHAT_INVITE_LINK = Item() # revokeChatInviteLink SET_CHAT_PHOTO = Item() # setChatPhoto DELETE_CHAT_PHOTO = Item() # deleteChatPhoto SET_CHAT_TITLE = Item() # setChatTitle diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index ab244252..5db66758 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -1550,28 +1550,43 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): result = await self.request(api.Methods.GET_FILE, payload) return types.File(**result) - async def kick_chat_member(self, chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer, - until_date: typing.Union[ - base.Integer, datetime.datetime, datetime.timedelta, None] = None) -> base.Boolean: + async def kick_chat_member(self, + chat_id: typing.Union[base.Integer, base.String], + user_id: base.Integer, + until_date: typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None] = None, + revoke_messages: typing.Optional[base.Boolean] = None, + ) -> base.Boolean: """ Use this method to kick a user from a group, a supergroup or a channel. - In the case of supergroups and channels, the user will not be able to return to the group - on their own using invite links, etc., unless unbanned first. + In the case of supergroups and channels, the user will not be able to return + to the chat on their own using invite links, etc., unless unbanned first. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ setting - is off in the target group. - Otherwise members may only be removed by the group's creator or by the member that added them. + The bot must be an administrator in the chat for this to work and must have + the appropriate admin rights. Source: https://core.telegram.org/bots/api#kickchatmember - :param chat_id: Unique identifier for the target group or username of the target supergroup or channel + :param chat_id: Unique identifier for the target group or username of the + target supergroup or channel (in the format @channelusername) :type chat_id: :obj:`typing.Union[base.Integer, base.String]` + :param user_id: Unique identifier of the target user :type user_id: :obj:`base.Integer` - :param until_date: Date when the user will be unbanned, unix time - :type until_date: :obj:`typing.Optional[base.Integer]` + + :param until_date: Date when the user will be unbanned. If user is banned + for more than 366 days or less than 30 seconds from the current time they + are considered to be banned forever. Applied for supergroups and channels + only. + :type until_date: :obj:`typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None` + + :param revoke_messages: Pass True to delete all messages from the chat for + the user that is being removed. If False, the user will be able to see + messages in the group that were sent before the user was removed. Always + True for supergroups and channels. + :type revoke_messages: :obj:`typing.Optional[base.Boolean]` + :return: Returns True on success :rtype: :obj:`base.Boolean` """ @@ -1675,10 +1690,12 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer, is_anonymous: typing.Optional[base.Boolean] = None, + can_manage_chat: typing.Optional[base.Boolean] = None, can_change_info: typing.Optional[base.Boolean] = None, can_post_messages: typing.Optional[base.Boolean] = None, can_edit_messages: typing.Optional[base.Boolean] = None, can_delete_messages: typing.Optional[base.Boolean] = None, + can_manage_voice_chats: typing.Optional[base.Boolean] = None, can_invite_users: typing.Optional[base.Boolean] = None, can_restrict_members: typing.Optional[base.Boolean] = None, can_pin_messages: typing.Optional[base.Boolean] = None, @@ -1700,6 +1717,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): :param is_anonymous: Pass True, if the administrator's presence in the chat is hidden :type is_anonymous: :obj:`typing.Optional[base.Boolean]` + :param can_manage_chat: Pass True, if the administrator can access the chat event log, chat statistics, + message statistics in channels, see channel members, see anonymous administrators in supergroups + and ignore slow mode. Implied by any other administrator privilege + :type can_manage_chat: :obj:`typing.Optional[base.Boolean]` + :param can_change_info: Pass True, if the administrator can change chat title, photo and other settings :type can_change_info: :obj:`typing.Optional[base.Boolean]` @@ -1712,6 +1734,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): :param can_delete_messages: Pass True, if the administrator can delete messages of other users :type can_delete_messages: :obj:`typing.Optional[base.Boolean]` + :param can_manage_voice_chats: Pass True, if the administrator can manage voice chats, supergroups only + :type can_manage_voice_chats: :obj:`typing.Optional[base.Boolean]` + :param can_invite_users: Pass True, if the administrator can invite new users to the chat :type can_invite_users: :obj:`typing.Optional[base.Boolean]` @@ -1789,6 +1814,100 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): result = await self.request(api.Methods.EXPORT_CHAT_INVITE_LINK, payload) return result + async def create_chat_invite_link(self, + chat_id: typing.Union[base.Integer, base.String], + expire_date: typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None], + member_limit: typing.Optional[base.Integer], + ) -> types.ChatInviteLink: + """ + Use this method to create an additional invite link for a chat. + The bot must be an administrator in the chat for this to work and must have + the appropriate admin rights. The link can be revoked using the method + revokeChatInviteLink. + + Source: https://core.telegram.org/bots/api#createchatinvitelink + + :param chat_id: Unique identifier for the target chat or username of the + target channel (in the format @channelusername) + :type chat_id: :obj:`typing.Union[base.Integer, base.String]` + + :param expire_date: Point in time when the link will expire + :type expire_date: :obj:`typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None]` + + :param member_limit: Maximum number of users that can be members of the chat + simultaneously after joining the chat via this invite link; 1-99999 + :type member_limit: :obj:`typing.Optional[base.Integer]` + + :return: the new invite link as ChatInviteLink object. + :rtype: :obj:`types.ChatInviteLink` + """ + expire_date = prepare_arg(expire_date) + payload = generate_payload(**locals()) + + result = await self.request(api.Methods.CREATE_CHAT_INVITE_LINK, payload) + return result + + async def edit_chat_invite_link(self, + chat_id: typing.Union[base.Integer, base.String], + invite_link: base.String, + expire_date: typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None], + member_limit: typing.Optional[base.Integer], + ) -> types.ChatInviteLink: + """ + Use this method to edit a non-primary invite link created by the bot. + The bot must be an administrator in the chat for this to work and must have + the appropriate admin rights. + + Source: https://core.telegram.org/bots/api#editchatinvitelink + + :param chat_id: Unique identifier for the target chat or username of the + target channel (in the format @channelusername) + :type chat_id: :obj:`typing.Union[base.Integer, base.String]` + + :param invite_link: The invite link to edit + :type invite_link: :obj:`base.String` + + :param expire_date: Point in time (Unix timestamp) when the link will expire + :type expire_date: :obj:`typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None]` + + :param member_limit: Maximum number of users that can be members of the chat + simultaneously after joining the chat via this invite link; 1-99999 + :type member_limit: :obj:`typing.Optional[base.Integer]` + + :return: edited invite link as a ChatInviteLink object. + """ + expire_date = prepare_arg(expire_date) + payload = generate_payload(**locals()) + + result = await self.request(api.Methods.EDIT_CHAT_INVITE_LINK, payload) + return result + + async def revoke_chat_invite_link(self, + chat_id: typing.Union[base.Integer, base.String], + invite_link: base.String, + ) -> types.ChatInviteLink: + """ + Use this method to revoke an invite link created by the bot. + If the primary link is revoked, a new link is automatically generated. + The bot must be an administrator in the chat for this to work and must have + the appropriate admin rights. + + Source: https://core.telegram.org/bots/api#revokechatinvitelink + + :param chat_id: Unique identifier for the target chat or username of the + target channel (in the format @channelusername) + :param invite_link: The invite link to revoke + :return: the revoked invite link as ChatInviteLink object + """ + payload = generate_payload(**locals()) + + result = await self.request(api.Methods.REVOKE_CHAT_INVITE_LINK, payload) + return result + async def set_chat_photo(self, chat_id: typing.Union[base.Integer, base.String], photo: base.InputFile) -> base.Boolean: """ diff --git a/aiogram/contrib/middlewares/logging.py b/aiogram/contrib/middlewares/logging.py index 308d0e10..82c2b50a 100644 --- a/aiogram/contrib/middlewares/logging.py +++ b/aiogram/contrib/middlewares/logging.py @@ -160,6 +160,26 @@ class LoggingMiddleware(BaseMiddleware): self.logger.debug(f"{HANDLED_STR[bool(len(results))]} poll answer [ID:{poll_answer.poll_id}] " f"from user [ID:{poll_answer.user.id}]") + async def on_pre_process_my_chat_member(self, my_chat_member_update, data): + self.logger.info(f"Received chat member update " + f"for user [ID:{my_chat_member_update.from_user.id}]. " + f"Old state: {my_chat_member_update.old_chat_member.to_python()} " + f"New state: {my_chat_member_update.new_chat_member.to_python()} ") + + async def on_post_process_my_chat_member(self, my_chat_member_update, results, data): + self.logger.debug(f"{HANDLED_STR[bool(len(results))]} my_chat_member " + f"for user [ID:{my_chat_member_update.from_user.id}]") + + async def on_pre_process_chat_member(self, chat_member_update, data): + self.logger.info(f"Received chat member update " + f"for user [ID:{chat_member_update.from_user.id}]. " + f"Old state: {chat_member_update.old_chat_member.to_python()} " + f"New state: {chat_member_update.new_chat_member.to_python()} ") + + async def on_post_process_chat_member(self, chat_member_update, results, data): + self.logger.debug(f"{HANDLED_STR[bool(len(results))]} chat_member " + f"for user [ID:{chat_member_update.from_user.id}]") + class LoggingFilter(logging.Filter): """ diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index b38d3af1..050f148a 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -78,6 +78,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin): self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query') self.poll_handlers = Handler(self, middleware_key='poll') self.poll_answer_handlers = Handler(self, middleware_key='poll_answer') + self.my_chat_member_handlers = Handler(self, middleware_key='my_chat_member') + self.chat_member_handlers = Handler(self, middleware_key='chat_member') self.errors_handlers = Handler(self, once=False, middleware_key='error') self.middleware = MiddlewareManager(self) @@ -163,6 +165,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin): self.edited_channel_post_handlers, self.callback_query_handlers, self.inline_query_handlers, + self.chat_member_handlers, ]) filters_factory.bind(IDFilter, event_handlers=[ self.message_handlers, @@ -171,6 +174,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin): self.edited_channel_post_handlers, self.callback_query_handlers, self.inline_query_handlers, + self.chat_member_handlers, + self.my_chat_member_handlers, ]) filters_factory.bind(IsReplyFilter, event_handlers=[ self.message_handlers, @@ -196,6 +201,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin): self.channel_post_handlers, self.edited_channel_post_handlers, self.callback_query_handlers, + self.my_chat_member_handlers, + self.chat_member_handlers ]) def __del__(self): @@ -286,6 +293,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin): types.PollAnswer.set_current(update.poll_answer) types.User.set_current(update.poll_answer.user) return await self.poll_answer_handlers.notify(update.poll_answer) + if update.my_chat_member: + types.ChatMemberUpdated.set_current(update.my_chat_member) + types.User.set_current(update.my_chat_member.from_user) + return await self.my_chat_member_handlers.notify(update.my_chat_member) + if update.chat_member: + types.ChatMemberUpdated.set_current(update.chat_member) + types.User.set_current(update.chat_member.from_user) + return await self.chat_member_handlers.notify(update.chat_member) except Exception as e: err = await self.errors_handlers.notify(update, e) if err: @@ -1005,6 +1020,118 @@ class Dispatcher(DataMixin, ContextInstanceMixin): return decorator + def register_my_chat_member_handler(self, + callback: typing.Callable, + *custom_filters, + run_task: typing.Optional[bool] = None, + **kwargs) -> None: + """ + Register handler for my_chat_member + + Example: + + .. code-block:: python3 + + dp.register_my_chat_member_handler(some_my_chat_member_handler) + + :param callback: + :param custom_filters: + :param run_task: run callback in task (no wait results) + :param kwargs: + """ + filters_set = self.filters_factory.resolve( + self.my_chat_member_handlers, + *custom_filters, + **kwargs, + ) + self.my_chat_member_handlers.register( + handler=self._wrap_async_task(callback, run_task), + filters=filters_set, + ) + + def my_chat_member_handler(self, *custom_filters, run_task=None, **kwargs): + """ + Decorator for my_chat_member handler + + Example: + + .. code-block:: python3 + + @dp.my_chat_member_handler() + async def some_handler(my_chat_member: types.ChatMemberUpdated) + + :param custom_filters: + :param run_task: run callback in task (no wait results) + :param kwargs: + """ + + def decorator(callback): + self.register_my_chat_member_handler( + callback, + *custom_filters, + run_task=run_task, + **kwargs, + ) + return callback + + return decorator + + def register_chat_member_handler(self, + callback: typing.Callable, + *custom_filters, + run_task: typing.Optional[bool] = None, + **kwargs) -> None: + """ + Register handler for chat_member + + Example: + + .. code-block:: python3 + + dp.register_chat_member_handler(some_chat_member_handler) + + :param callback: + :param custom_filters: + :param run_task: run callback in task (no wait results) + :param kwargs: + """ + filters_set = self.filters_factory.resolve( + self.chat_member_handlers, + *custom_filters, + **kwargs, + ) + self.chat_member_handlers.register( + handler=self._wrap_async_task(callback, run_task), + filters=filters_set, + ) + + def chat_member_handler(self, *custom_filters, run_task=None, **kwargs): + """ + Decorator for chat_member handler + + Example: + + .. code-block:: python3 + + @dp.chat_member_handler() + async def some_handler(chat_member: types.ChatMemberUpdated) + + :param custom_filters: + :param run_task: run callback in task (no wait results) + :param kwargs: + """ + + def decorator(callback): + self.register_chat_member_handler( + callback, + *custom_filters, + run_task=run_task, + **kwargs, + ) + return callback + + return decorator + def register_errors_handler(self, callback, *custom_filters, exception=None, run_task=None, **kwargs): """ Register handler for errors diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index 20317f57..ded3e9fd 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -10,7 +10,7 @@ from babel.support import LazyProxy from aiogram import types from aiogram.dispatcher.filters.filters import BoundFilter, Filter -from aiogram.types import CallbackQuery, ChatType, InlineQuery, Message, Poll +from aiogram.types import CallbackQuery, ChatType, InlineQuery, Message, Poll, ChatMemberUpdated ChatIDArgumentType = typing.Union[typing.Iterable[typing.Union[int, str]], str, int] @@ -604,7 +604,7 @@ class IDFilter(Filter): return result - async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]): + async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, ChatMemberUpdated]): if isinstance(obj, Message): user_id = None if obj.from_user is not None: @@ -619,6 +619,9 @@ class IDFilter(Filter): elif isinstance(obj, InlineQuery): user_id = obj.from_user.id chat_id = None + elif isinstance(obj, ChatMemberUpdated): + user_id = obj.from_user.id + chat_id = obj.chat.id else: return False @@ -663,19 +666,21 @@ class AdminFilter(Filter): return result - async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]) -> bool: + async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, ChatMemberUpdated]) -> bool: user_id = obj.from_user.id if self._check_current: if isinstance(obj, Message): - message = obj + chat = obj.chat elif isinstance(obj, CallbackQuery) and obj.message: - message = obj.message + chat = obj.message.chat + elif isinstance(obj, ChatMemberUpdated): + chat = obj.chat else: return False - if message.chat.type == ChatType.PRIVATE: # there is no admin in private chats + if chat.type == ChatType.PRIVATE: # there is no admin in private chats return False - chat_ids = [message.chat.id] + chat_ids = [chat.id] else: chat_ids = self._chat_ids @@ -719,11 +724,13 @@ class ChatTypeFilter(BoundFilter): self.chat_type: typing.Set[str] = set(chat_type) - async def check(self, obj: Union[Message, CallbackQuery]): + async def check(self, obj: Union[Message, CallbackQuery, ChatMemberUpdated]): if isinstance(obj, Message): obj = obj.chat elif isinstance(obj, CallbackQuery): obj = obj.message.chat + elif isinstance(obj, ChatMemberUpdated): + obj = obj.chat else: warnings.warn("ChatTypeFilter doesn't support %s as input", type(obj)) return False diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index 26201130..1dfa519f 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -7,8 +7,10 @@ from .bot_command import BotCommand from .callback_game import CallbackGame from .callback_query import CallbackQuery from .chat import Chat, ChatActions, ChatType +from .chat_invite_link import ChatInviteLink from .chat_location import ChatLocation from .chat_member import ChatMember, ChatMemberStatus +from .chat_member_updated import ChatMemberUpdated from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto from .chosen_inline_result import ChosenInlineResult @@ -40,6 +42,7 @@ from .location import Location from .login_url import LoginUrl from .mask_position import MaskPosition from .message import ContentType, ContentTypes, Message, ParseMode +from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged from .message_entity import MessageEntity, MessageEntityType from .message_id import MessageId from .order_info import OrderInfo @@ -67,6 +70,9 @@ from .venue import Venue from .video import Video from .video_note import VideoNote from .voice import Voice +from .voice_chat_ended import VoiceChatEnded +from .voice_chat_participants_invited import VoiceChatParticipantsInvited +from .voice_chat_started import VoiceChatStarted from .webhook_info import WebhookInfo __all__ = ( @@ -79,9 +85,11 @@ __all__ = ( 'CallbackQuery', 'Chat', 'ChatActions', + 'ChatInviteLink', 'ChatLocation', 'ChatMember', 'ChatMemberStatus', + 'ChatMemberUpdated', 'ChatPermissions', 'ChatPhoto', 'ChatType', @@ -143,6 +151,7 @@ __all__ = ( 'MaskPosition', 'MediaGroup', 'Message', + 'MessageAutoDeleteTimerChanged', 'MessageEntity', 'MessageEntityType', 'MessageId', @@ -180,6 +189,9 @@ __all__ = ( 'Video', 'VideoNote', 'Voice', + 'VoiceChatEnded', + 'VoiceChatParticipantsInvited', + 'VoiceChatStarted', 'WebhookInfo', 'base', 'fields', diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 4f062b49..957ee78b 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -5,6 +5,7 @@ import datetime import typing from . import base, fields +from .chat_invite_link import ChatInviteLink from .chat_location import ChatLocation from .chat_member import ChatMember from .chat_permissions import ChatPermissions @@ -185,30 +186,47 @@ class Chat(base.TelegramObject): """ return await self.bot.set_chat_description(self.id, description) - async def kick(self, user_id: base.Integer, - until_date: typing.Union[ - base.Integer, datetime.datetime, datetime.timedelta, None] = None) -> base.Boolean: + async def kick(self, + user_id: base.Integer, + until_date: typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None] = None, + revoke_messages: typing.Optional[base.Boolean] = None, + ) -> base.Boolean: """ Use this method to kick a user from a group, a supergroup or a channel. - In the case of supergroups and channels, the user will not be able to return to the group - on their own using invite links, etc., unless unbanned first. + In the case of supergroups and channels, the user will not be able to return + to the chat on their own using invite links, etc., unless unbanned first. - The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ setting - is off in the target group. - Otherwise members may only be removed by the group's creator or by the member that added them. + The bot must be an administrator in the chat for this to work and must have + the appropriate admin rights. Source: https://core.telegram.org/bots/api#kickchatmember :param user_id: Unique identifier of the target user :type user_id: :obj:`base.Integer` - :param until_date: Date when the user will be unbanned, unix time. - :type until_date: :obj:`typing.Optional[base.Integer]` - :return: Returns True on success. + + :param until_date: Date when the user will be unbanned. If user is banned + for more than 366 days or less than 30 seconds from the current time they + are considered to be banned forever. Applied for supergroups and channels + only. + :type until_date: :obj:`typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None` + + :param revoke_messages: Pass True to delete all messages from the chat for + the user that is being removed. If False, the user will be able to see + messages in the group that were sent before the user was removed. Always + True for supergroups and channels. + :type revoke_messages: :obj:`typing.Optional[base.Boolean]` + + :return: Returns True on success :rtype: :obj:`base.Boolean` """ - return await self.bot.kick_chat_member(self.id, user_id=user_id, until_date=until_date) + return await self.bot.kick_chat_member( + chat_id=self.id, + user_id=user_id, + until_date=until_date, + revoke_messages=revoke_messages, + ) async def unban(self, user_id: base.Integer, @@ -554,6 +572,41 @@ class Chat(base.TelegramObject): return self.invite_link + async def create_invite_link(self, + expire_date: typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None], + member_limit: typing.Optional[base.Integer], + ) -> ChatInviteLink: + """ Shortcut for createChatInviteLink method. """ + return await self.bot.create_chat_invite_link( + chat_id=self.id, + expire_date=expire_date, + member_limit=member_limit, + ) + + async def edit_invite_link(self, + invite_link: base.String, + expire_date: typing.Union[base.Integer, datetime.datetime, + datetime.timedelta, None], + member_limit: typing.Optional[base.Integer], + ) -> ChatInviteLink: + """ Shortcut for editChatInviteLink method. """ + return await self.bot.edit_chat_invite_link( + chat_id=self.id, + invite_link=invite_link, + expire_date=expire_date, + member_limit=member_limit, + ) + + async def revoke_invite_link(self, + invite_link: base.String, + ) -> ChatInviteLink: + """ Shortcut for revokeChatInviteLink method. """ + return await self.bot.revoke_chat_invite_link( + chat_id=self.id, + invite_link=invite_link, + ) + def __int__(self): return self.id diff --git a/aiogram/types/chat_invite_link.py b/aiogram/types/chat_invite_link.py new file mode 100644 index 00000000..55794780 --- /dev/null +++ b/aiogram/types/chat_invite_link.py @@ -0,0 +1,20 @@ +from datetime import datetime + +from . import base +from . import fields +from .user import User + + +class ChatInviteLink(base.TelegramObject): + """ + Represents an invite link for a chat. + + https://core.telegram.org/bots/api#chatinvitelink + """ + + invite_link: base.String = fields.Field() + creator: User = fields.Field(base=User) + is_primary: base.Boolean = fields.Field() + is_revoked: base.Boolean = fields.Field() + expire_date: datetime = fields.DateTimeField() + member_limit: base.Integer = fields.Field() diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py index 4aa52b80..c48a91d0 100644 --- a/aiogram/types/chat_member.py +++ b/aiogram/types/chat_member.py @@ -16,22 +16,24 @@ class ChatMember(base.TelegramObject): status: base.String = fields.Field() custom_title: base.String = fields.Field() is_anonymous: base.Boolean = fields.Field() - until_date: datetime.datetime = fields.DateTimeField() can_be_edited: base.Boolean = fields.Field() - can_change_info: base.Boolean = fields.Field() + can_manage_chat: base.Boolean = fields.Field() can_post_messages: base.Boolean = fields.Field() can_edit_messages: base.Boolean = fields.Field() can_delete_messages: base.Boolean = fields.Field() - can_invite_users: base.Boolean = fields.Field() + can_manage_voice_chats: base.Boolean = fields.Field() can_restrict_members: base.Boolean = fields.Field() - can_pin_messages: base.Boolean = fields.Field() can_promote_members: base.Boolean = fields.Field() + can_change_info: base.Boolean = fields.Field() + can_invite_users: base.Boolean = fields.Field() + can_pin_messages: base.Boolean = fields.Field() is_member: base.Boolean = fields.Field() can_send_messages: base.Boolean = fields.Field() can_send_media_messages: base.Boolean = fields.Field() can_send_polls: base.Boolean = fields.Field() can_send_other_messages: base.Boolean = fields.Field() can_add_web_page_previews: base.Boolean = fields.Field() + until_date: datetime.datetime = fields.DateTimeField() def is_chat_creator(self) -> bool: return ChatMemberStatus.is_chat_creator(self.status) diff --git a/aiogram/types/chat_member_updated.py b/aiogram/types/chat_member_updated.py new file mode 100644 index 00000000..7c6a124f --- /dev/null +++ b/aiogram/types/chat_member_updated.py @@ -0,0 +1,22 @@ +import datetime + +from . import base +from . import fields +from .chat import Chat +from .chat_invite_link import ChatInviteLink +from .chat_member import ChatMember +from .user import User + + +class ChatMemberUpdated(base.TelegramObject): + """ + This object represents changes in the status of a chat member. + + https://core.telegram.org/bots/api#chatmemberupdated + """ + chat: Chat = fields.Field(base=Chat) + from_user: User = fields.Field(base=User) + date: datetime.datetime = fields.DateTimeField() + old_chat_member: ChatMember = fields.Field(base=ChatMember) + new_chat_member: ChatMember = fields.Field(base=ChatMember) + invite_link: ChatInviteLink = fields.Field(base=ChatInviteLink) diff --git a/aiogram/types/dice.py b/aiogram/types/dice.py index 70c50e09..c4f2725e 100644 --- a/aiogram/types/dice.py +++ b/aiogram/types/dice.py @@ -3,9 +3,7 @@ from . import base, fields class Dice(base.TelegramObject): """ - This object represents a dice with random value from 1 to 6. - (Yes, we're aware of the “proper” singular of die. - But it's awkward, and we decided to help it change. One dice at a time!) + This object represents an animated emoji that displays a random value. https://core.telegram.org/bots/api#dice """ @@ -19,3 +17,4 @@ class DiceEmoji: BASKETBALL = '🏀' FOOTBALL = '⚽' SLOT_MACHINE = '🎰' + BOWLING = '🎳' diff --git a/aiogram/types/message.py b/aiogram/types/message.py index b018ea11..4b7e4475 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -4,10 +4,6 @@ import datetime import functools import typing -from ..utils import helper -from ..utils import markdown as md -from ..utils.deprecated import deprecated -from ..utils.text_decorations import html_decoration, markdown_decoration from . import base, fields from .animation import Animation from .audio import Audio @@ -21,6 +17,7 @@ from .inline_keyboard import InlineKeyboardMarkup from .input_media import InputMedia, MediaGroup from .invoice import Invoice from .location import Location +from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged from .message_entity import MessageEntity from .message_id import MessageId from .passport_data import PassportData @@ -35,6 +32,13 @@ from .venue import Venue from .video import Video from .video_note import VideoNote from .voice import Voice +from .voice_chat_ended import VoiceChatEnded +from .voice_chat_participants_invited import VoiceChatParticipantsInvited +from .voice_chat_started import VoiceChatStarted +from ..utils import helper +from ..utils import markdown as md +from ..utils.deprecated import deprecated +from ..utils.text_decorations import html_decoration, markdown_decoration class Message(base.TelegramObject): @@ -86,6 +90,7 @@ class Message(base.TelegramObject): group_chat_created: base.Boolean = fields.Field() supergroup_chat_created: base.Boolean = fields.Field() channel_chat_created: base.Boolean = fields.Field() + message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged = fields.Field(base=MessageAutoDeleteTimerChanged) migrate_to_chat_id: base.Integer = fields.Field() migrate_from_chat_id: base.Integer = fields.Field() pinned_message: Message = fields.Field(base="Message") @@ -94,6 +99,9 @@ class Message(base.TelegramObject): connected_website: base.String = fields.Field() passport_data: PassportData = fields.Field(base=PassportData) proximity_alert_triggered: ProximityAlertTriggered = fields.Field(base=ProximityAlertTriggered) + voice_chat_started: VoiceChatStarted = fields.Field(base=VoiceChatStarted) + voice_chat_ended: VoiceChatEnded = fields.Field(base=VoiceChatEnded) + voice_chat_participants_invited: VoiceChatParticipantsInvited = fields.Field(base=VoiceChatParticipantsInvited) reply_markup: InlineKeyboardMarkup = fields.Field(base=InlineKeyboardMarkup) @property @@ -139,6 +147,8 @@ class Message(base.TelegramObject): return ContentType.SUCCESSFUL_PAYMENT if self.connected_website: return ContentType.CONNECTED_WEBSITE + if self.message_auto_delete_timer_changed: + return ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED if self.migrate_from_chat_id: return ContentType.MIGRATE_FROM_CHAT_ID if self.migrate_to_chat_id: @@ -157,6 +167,12 @@ class Message(base.TelegramObject): return ContentType.PASSPORT_DATA if self.proximity_alert_triggered: return ContentType.PROXIMITY_ALERT_TRIGGERED + if self.voice_chat_started: + return ContentType.VOICE_CHAT_STARTED + if self.voice_chat_ended: + return ContentType.VOICE_CHAT_ENDED + if self.voice_chat_participants_invited: + return ContentType.VOICE_CHAT_PARTICIPANTS_INVITED return ContentType.UNKNOWN @@ -2980,6 +2996,7 @@ class ContentType(helper.Helper): INVOICE = helper.Item() # invoice SUCCESSFUL_PAYMENT = helper.Item() # successful_payment CONNECTED_WEBSITE = helper.Item() # connected_website + MESSAGE_AUTO_DELETE_TIMER_CHANGED = helper.Item() # message_auto_delete_timer_changed MIGRATE_TO_CHAT_ID = helper.Item() # migrate_to_chat_id MIGRATE_FROM_CHAT_ID = helper.Item() # migrate_from_chat_id PINNED_MESSAGE = helper.Item() # pinned_message @@ -2989,6 +3006,9 @@ class ContentType(helper.Helper): GROUP_CHAT_CREATED = helper.Item() # group_chat_created PASSPORT_DATA = helper.Item() # passport_data PROXIMITY_ALERT_TRIGGERED = helper.Item() # proximity_alert_triggered + VOICE_CHAT_STARTED = helper.Item() # voice_chat_started + VOICE_CHAT_ENDED = helper.Item() # voice_chat_ended + VOICE_CHAT_PARTICIPANTS_INVITED = helper.Item() # voice_chat_participants_invited UNKNOWN = helper.Item() # unknown ANY = helper.Item() # any diff --git a/aiogram/types/message_auto_delete_timer_changed.py b/aiogram/types/message_auto_delete_timer_changed.py new file mode 100644 index 00000000..8b882d1b --- /dev/null +++ b/aiogram/types/message_auto_delete_timer_changed.py @@ -0,0 +1,11 @@ +from . import base +from . import fields + + +class MessageAutoDeleteTimerChanged(base.TelegramObject): + """ + This object represents a service message about a change in auto-delete timer settings. + + https://core.telegram.org/bots/api#messageautodeletetimerchanged + """ + message_auto_delete_time: base.Integer = fields.Field() diff --git a/aiogram/types/update.py b/aiogram/types/update.py index 9d1afacc..c8c4b58d 100644 --- a/aiogram/types/update.py +++ b/aiogram/types/update.py @@ -3,6 +3,7 @@ from __future__ import annotations from . import base from . import fields from .callback_query import CallbackQuery +from .chat_member_updated import ChatMemberUpdated from .chosen_inline_result import ChosenInlineResult from .inline_query import InlineQuery from .message import Message @@ -31,6 +32,8 @@ class Update(base.TelegramObject): pre_checkout_query: PreCheckoutQuery = fields.Field(base=PreCheckoutQuery) poll: Poll = fields.Field(base=Poll) poll_answer: PollAnswer = fields.Field(base=PollAnswer) + my_chat_member: ChatMemberUpdated = fields.Field(base=ChatMemberUpdated) + chat_member: ChatMemberUpdated = fields.Field(base=ChatMemberUpdated) def __hash__(self): return self.update_id @@ -61,6 +64,8 @@ class AllowedUpdates(helper.Helper): PRE_CHECKOUT_QUERY = helper.ListItem() # pre_checkout_query POLL = helper.ListItem() # poll POLL_ANSWER = helper.ListItem() # poll_answer + MY_CHAT_MEMBER = helper.ListItem() # my_chat_member + CHAT_MEMBER = helper.ListItem() # chat_member CHOSEN_INLINE_QUERY = deprecated.DeprecatedReadOnlyClassVar( "`CHOSEN_INLINE_QUERY` is a deprecated value for allowed update. " diff --git a/aiogram/types/voice_chat_ended.py b/aiogram/types/voice_chat_ended.py new file mode 100644 index 00000000..f1bb1f05 --- /dev/null +++ b/aiogram/types/voice_chat_ended.py @@ -0,0 +1,13 @@ +from . import base +from . import fields +from . import mixins + + +class VoiceChatEnded(base.TelegramObject, mixins.Downloadable): + """ + This object represents a service message about a voice chat ended in the chat. + + https://core.telegram.org/bots/api#voicechatended + """ + + duration: base.Integer = fields.Field() diff --git a/aiogram/types/voice_chat_participants_invited.py b/aiogram/types/voice_chat_participants_invited.py new file mode 100644 index 00000000..fbd0a457 --- /dev/null +++ b/aiogram/types/voice_chat_participants_invited.py @@ -0,0 +1,16 @@ +import typing + +from . import base +from . import fields +from . import mixins +from .user import User + + +class VoiceChatParticipantsInvited(base.TelegramObject, mixins.Downloadable): + """ + This object represents a service message about new members invited to a voice chat. + + https://core.telegram.org/bots/api#voicechatparticipantsinvited + """ + + users: typing.List[User] = fields.ListField(base=User) diff --git a/aiogram/types/voice_chat_started.py b/aiogram/types/voice_chat_started.py new file mode 100644 index 00000000..3cd76322 --- /dev/null +++ b/aiogram/types/voice_chat_started.py @@ -0,0 +1,12 @@ +from . import base +from . import mixins + + +class VoiceChatStarted(base.TelegramObject, mixins.Downloadable): + """ + This object represents a service message about a voice chat started in the chat. + Currently holds no information. + + https://core.telegram.org/bots/api#voicechatstarted + """ + pass From 1354f8d58c5af1070cef4ed198a406efe561e969 Mon Sep 17 00:00:00 2001 From: Arseniy Kulikov <62447812+klkvr@users.noreply.github.com> Date: Sun, 14 Mar 2021 19:06:55 +0300 Subject: [PATCH 09/19] added answer_chat_action() method for types.Message (#501) --- aiogram/types/message.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 4b7e4475..e97231b6 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -1416,6 +1416,30 @@ class Message(base.TelegramObject): allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, ) + + async def answer_chat_action( + self, + action: base.String, + ) -> base.Boolean: + """ + Use this method when you need to tell the user that something is happening on the bot's side. + The status is set for 5 seconds or less + (when a message arrives from your bot, Telegram clients clear its typing status). + + We only recommend using this method when a response from the bot will take + a noticeable amount of time to arrive. + + Source: https://core.telegram.org/bots/api#sendchataction + + :param action: Type of action to broadcast + :type action: :obj:`base.String` + :return: Returns True on success + :rtype: :obj:`base.Boolean` + """ + return await self.bot.send_chat_action( + chat_id=self.chat.id, + action=action, + ) async def reply( self, From 5b1bed79428218e5c21db28543bfbdce34d99581 Mon Sep 17 00:00:00 2001 From: darksidecat <58224121+darksidecat@users.noreply.github.com> Date: Sun, 14 Mar 2021 18:10:40 +0200 Subject: [PATCH 10/19] fix bug in getting user_id, chat_id from context (#520) * fix bug in getting user_id, chat_id from context (need User.id for future use, not User object) * requested changes: get current() can return None add check this case --- aiogram/dispatcher/dispatcher.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 050f148a..a2736cf7 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -1211,8 +1211,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin): if rate is None: rate = self.throttling_rate_limit if user_id is None and chat_id is None: - user_id = types.User.get_current().id - chat_id = types.Chat.get_current().id + chat_obj = types.Chat.get_current() + chat_id = chat_obj.id if chat_obj else None + + user_obj = types.User.get_current() + user_id = user_obj.id if user_obj else None # Detect current time now = time.time() @@ -1263,8 +1266,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin): raise RuntimeError('This storage does not provide Leaky Bucket') if user_id is None and chat_id is None: - user_id = types.User.get_current() - chat_id = types.Chat.get_current() + chat_obj = types.Chat.get_current() + chat_id = chat_obj.id if chat_obj else None + + user_obj = types.User.get_current() + user_id = user_obj.id if user_obj else None bucket = await self.storage.get_bucket(chat=chat_id, user=user_id) data = bucket.get(key, {}) @@ -1285,8 +1291,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin): raise RuntimeError('This storage does not provide Leaky Bucket') if user_id is None and chat_id is None: - user_id = types.User.get_current() - chat_id = types.Chat.get_current() + chat_obj = types.Chat.get_current() + chat_id = chat_obj.id if chat_obj else None + + user_obj = types.User.get_current() + user_id = user_obj.id if user_obj else None bucket = await self.storage.get_bucket(chat=chat_id, user=user_id) if bucket and key in bucket: From d9de7994798c48dad30d86a9636271a225540535 Mon Sep 17 00:00:00 2001 From: Fenicu Date: Sun, 14 Mar 2021 19:11:48 +0300 Subject: [PATCH 11/19] Add new types in message.send_copy method (#511) --- aiogram/types/message.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index e97231b6..2e8402e7 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -2931,6 +2931,14 @@ class Message(base.TelegramObject): return await self.bot.send_poll( question=self.poll.question, options=[option.text for option in self.poll.options], + is_anonymous=self.poll.is_anonymous, + allows_multiple_answers=self.poll.allows_multiple_answers + **kwargs, + ) + elif self.dice: + kwargs.pop("parse_mode") + return await self.bot.send_dice( + emoji=self.dice.emoji, **kwargs, ) else: From 5254329c2bc68f97334ca409de24535c9349743f Mon Sep 17 00:00:00 2001 From: Daniil Kovalenko <40635760+WhiteMemory99@users.noreply.github.com> Date: Sun, 14 Mar 2021 23:13:57 +0700 Subject: [PATCH 12/19] Fix types.MediaGroup methods (#514) --- aiogram/types/input_media.py | 56 ++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/aiogram/types/input_media.py b/aiogram/types/input_media.py index 2d325d89..4d03daec 100644 --- a/aiogram/types/input_media.py +++ b/aiogram/types/input_media.py @@ -311,76 +311,96 @@ class MediaGroup(base.TelegramObject): self.attach(animation) ''' - def attach_audio(self, audio: base.InputFile, + def attach_audio(self, audio: typing.Union[InputMediaAudio, base.InputFile], thumb: typing.Union[base.InputFile, base.String] = None, caption: base.String = None, - width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None, performer: base.String = None, title: base.String = None, - parse_mode: base.String = None): + parse_mode: base.String = None, + caption_entities: typing.Optional[typing.List[MessageEntity]] = None): """ - Attach animation + Attach audio :param audio: :param thumb: :param caption: - :param width: - :param height: :param duration: :param performer: :param title: :param parse_mode: + :param caption_entities: """ if not isinstance(audio, InputMedia): audio = InputMediaAudio(media=audio, thumb=thumb, caption=caption, - width=width, height=height, duration=duration, + duration=duration, performer=performer, title=title, - parse_mode=parse_mode) + parse_mode=parse_mode, + caption_entities=caption_entities) self.attach(audio) - def attach_document(self, document: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None, - caption: base.String = None, parse_mode: base.String = None): + def attach_document(self, document: typing.Union[InputMediaDocument, base.InputFile], + thumb: typing.Union[base.InputFile, base.String] = None, + caption: base.String = None, parse_mode: base.String = None, + caption_entities: typing.Optional[typing.List[MessageEntity]] = None, + disable_content_type_detection: typing.Optional[base.Boolean] = None): """ Attach document - - :param parse_mode: + + :param document: :param caption: :param thumb: - :param document: + :param parse_mode: + :param caption_entities: + :param disable_content_type_detection: """ if not isinstance(document, InputMedia): - document = InputMediaDocument(media=document, thumb=thumb, caption=caption, parse_mode=parse_mode) + document = InputMediaDocument(media=document, thumb=thumb, caption=caption, + parse_mode=parse_mode, caption_entities=caption_entities, + disable_content_type_detection=disable_content_type_detection) self.attach(document) def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile], - caption: base.String = None): + caption: base.String = None, parse_mode: base.String = None, + caption_entities: typing.Optional[typing.List[MessageEntity]] = None): """ Attach photo :param photo: :param caption: + :param parse_mode: + :param caption_entities: """ if not isinstance(photo, InputMedia): - photo = InputMediaPhoto(media=photo, caption=caption) + photo = InputMediaPhoto(media=photo, caption=caption, parse_mode=parse_mode, + caption_entities=caption_entities) self.attach(photo) def attach_video(self, video: typing.Union[InputMediaVideo, base.InputFile], thumb: typing.Union[base.InputFile, base.String] = None, caption: base.String = None, - width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None): + width: base.Integer = None, height: base.Integer = None, + duration: base.Integer = None, parse_mode: base.String = None, + caption_entities: typing.Optional[typing.List[MessageEntity]] = None, + supports_streaming: base.Boolean = None): """ Attach video :param video: + :param thumb: :param caption: :param width: :param height: :param duration: + :param parse_mode: + :param caption_entities: + :param supports_streaming: """ if not isinstance(video, InputMedia): video = InputMediaVideo(media=video, thumb=thumb, caption=caption, - width=width, height=height, duration=duration) + width=width, height=height, duration=duration, + parse_mode=parse_mode, caption_entities=caption_entities, + supports_streaming=supports_streaming) self.attach(video) def to_python(self) -> typing.List: From 9bb3a5dccfb4511a700d670118cb3e9485e9f70e Mon Sep 17 00:00:00 2001 From: Andrew <11490628+andrew000@users.noreply.github.com> Date: Sun, 14 Mar 2021 18:14:40 +0200 Subject: [PATCH 13/19] ADD Exceptions: (#512) * CantRestrictChatOwner * UserIsAnAdministratorOfTheChat --- aiogram/utils/exceptions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/aiogram/utils/exceptions.py b/aiogram/utils/exceptions.py index d38ff009..9a1606a6 100644 --- a/aiogram/utils/exceptions.py +++ b/aiogram/utils/exceptions.py @@ -494,6 +494,20 @@ class MethodIsNotAvailable(BadRequest): match = "Method is available only for supergroups" +class CantRestrictChatOwner(BadRequest): + """ + Raises when bot restricts the chat owner + """ + match = 'Can\'t remove chat owner' + + +class UserIsAnAdministratorOfTheChat(BadRequest): + """ + Raises when bot restricts the chat admin + """ + match = 'User is an administrator of the chat' + + class NotFound(TelegramAPIError, _MatchErrorMixin): __group = True From b2b7ddb6396fe6e415e4461648466cb7e8148861 Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 14 Mar 2021 19:20:37 +0300 Subject: [PATCH 14/19] feat: an example of intergration between externally created Application and dispatcher (#433) * feat: an example of intergration between externally created Application and dispatcher * fix: imports * chore: fix EOF * chore: fix comment --- examples/separate_api_route_example.py | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 examples/separate_api_route_example.py diff --git a/examples/separate_api_route_example.py b/examples/separate_api_route_example.py new file mode 100644 index 00000000..2f3cc61c --- /dev/null +++ b/examples/separate_api_route_example.py @@ -0,0 +1,34 @@ +# NOTE: This is an example of an integration between +# externally created Application object and the aiogram's dispatcher +# This can be used for a custom route, for instance + +from aiogram import Bot, Dispatcher, types +from aiogram.dispatcher.webhook import configure_app +from aiohttp import web + + +bot = Bot(token=config.bot_token) +dp = Dispatcher(bot) + + + +@dp.message_handler(commands=["start"]) +async def cmd_start(message: types.Message): + await message.reply("start!") + + +# handle /api route +async def api_handler(request): + return web.json_response({"status": "OK"}, status=200) + + +app = web.Application() +# add a custom route +app.add_routes([web.post('/api', api_handler)]) +# every request to /bot route will be retransmitted to dispatcher to be handled +# as a bot update +configure_app(dp, app, "/bot") + + +if __name__ == '__main__': + web.run_app(app, port=9000) From cd047e8d017d86e4d3fa926fd7e747d2396f4cef Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 14 Mar 2021 18:27:38 +0200 Subject: [PATCH 15/19] Removed deprecation warning from Message.send_copy --- aiogram/types/message.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 2e8402e7..7f083119 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -37,7 +37,6 @@ from .voice_chat_participants_invited import VoiceChatParticipantsInvited from .voice_chat_started import VoiceChatStarted from ..utils import helper from ..utils import markdown as md -from ..utils.deprecated import deprecated from ..utils.text_decorations import html_decoration, markdown_decoration @@ -2824,11 +2823,6 @@ class Message(base.TelegramObject): message_id=self.message_id, ) - @deprecated( - "This method deprecated since Bot API 4.5. Use method `copy_to` instead. \n" - "Read more: https://core.telegram.org/bots/api#copymessage", - stacklevel=3 - ) async def send_copy( self: Message, chat_id: typing.Union[str, int], From 76955bf8f1b0e67d6ada302ffd7c3ffe4d9c7a18 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 14 Mar 2021 20:17:40 +0200 Subject: [PATCH 16/19] Fixed optional arguments in new methods (API 5.1) --- aiogram/bot/bot.py | 10 +++++----- aiogram/types/chat.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index 5db66758..85be7fd0 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -1579,7 +1579,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): are considered to be banned forever. Applied for supergroups and channels only. :type until_date: :obj:`typing.Union[base.Integer, datetime.datetime, - datetime.timedelta, None` + datetime.timedelta, None]` :param revoke_messages: Pass True to delete all messages from the chat for the user that is being removed. If False, the user will be able to see @@ -1817,8 +1817,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): async def create_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String], expire_date: typing.Union[base.Integer, datetime.datetime, - datetime.timedelta, None], - member_limit: typing.Optional[base.Integer], + datetime.timedelta, None] = None, + member_limit: typing.Optional[base.Integer] = None, ) -> types.ChatInviteLink: """ Use this method to create an additional invite link for a chat. @@ -1853,8 +1853,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): chat_id: typing.Union[base.Integer, base.String], invite_link: base.String, expire_date: typing.Union[base.Integer, datetime.datetime, - datetime.timedelta, None], - member_limit: typing.Optional[base.Integer], + datetime.timedelta, None] = None, + member_limit: typing.Optional[base.Integer] = None, ) -> types.ChatInviteLink: """ Use this method to edit a non-primary invite link created by the bot. diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 957ee78b..b9e03983 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -210,7 +210,7 @@ class Chat(base.TelegramObject): are considered to be banned forever. Applied for supergroups and channels only. :type until_date: :obj:`typing.Union[base.Integer, datetime.datetime, - datetime.timedelta, None` + datetime.timedelta, None]` :param revoke_messages: Pass True to delete all messages from the chat for the user that is being removed. If False, the user will be able to see @@ -574,8 +574,8 @@ class Chat(base.TelegramObject): async def create_invite_link(self, expire_date: typing.Union[base.Integer, datetime.datetime, - datetime.timedelta, None], - member_limit: typing.Optional[base.Integer], + datetime.timedelta, None] = None, + member_limit: typing.Optional[base.Integer] = None, ) -> ChatInviteLink: """ Shortcut for createChatInviteLink method. """ return await self.bot.create_chat_invite_link( @@ -587,8 +587,8 @@ class Chat(base.TelegramObject): async def edit_invite_link(self, invite_link: base.String, expire_date: typing.Union[base.Integer, datetime.datetime, - datetime.timedelta, None], - member_limit: typing.Optional[base.Integer], + datetime.timedelta, None] = None, + member_limit: typing.Optional[base.Integer] = None, ) -> ChatInviteLink: """ Shortcut for editChatInviteLink method. """ return await self.bot.edit_chat_invite_link( From 6c423d2b92ee8fbbabd3a97ca3682624741422bc Mon Sep 17 00:00:00 2001 From: Stefan <67127306+MEDIOFF@users.noreply.github.com> Date: Sun, 14 Mar 2021 21:39:29 +0300 Subject: [PATCH 17/19] Update safe_split_text function, added split_separator param (#515) Co-authored-by: Stefan Vasilenko --- aiogram/utils/parts.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aiogram/utils/parts.py b/aiogram/utils/parts.py index e03f7bcc..b4bb9d67 100644 --- a/aiogram/utils/parts.py +++ b/aiogram/utils/parts.py @@ -15,12 +15,13 @@ def split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]: return [text[i:i + length] for i in range(0, len(text), length)] -def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]: +def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH, split_separator: str = ' ') -> typing.List[str]: """ Split long text :param text: :param length: + :param split_separator :return: """ # TODO: More informative description @@ -30,7 +31,7 @@ def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[ while temp_text: if len(temp_text) > length: try: - split_pos = temp_text[:length].rindex(' ') + split_pos = temp_text[:length].rindex(split_separator) except ValueError: split_pos = length if split_pos < length // 4 * 3: From 8612a64b3c7b5c6c58c56a8fb9a6069627ffd017 Mon Sep 17 00:00:00 2001 From: monte-monte <6649967+monte-monte@users.noreply.github.com> Date: Sun, 14 Mar 2021 20:40:52 +0200 Subject: [PATCH 18/19] Update builtin.py (#510) * Update builtin.py In StateFilter check if object is CallbackQuery because it has different structure compared to simple message. This change prevents https://github.com/aiogram/aiogram/issues/508 bug. * Update aiogram/dispatcher/filters/builtin.py Co-authored-by: Alex Root Junior --- aiogram/dispatcher/filters/builtin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index ded3e9fd..762c8505 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -529,6 +529,8 @@ class StateFilter(BoundFilter): self.states = states def get_target(self, obj): + if isinstance(obj, CallbackQuery): + return getattr(getattr(getattr(obj, 'message', None),'chat', None), 'id', None), getattr(getattr(obj, 'from_user', None), 'id', None) return getattr(getattr(obj, 'chat', None), 'id', None), getattr(getattr(obj, 'from_user', None), 'id', None) async def check(self, obj): From cce3a5fbd0bba955e9d8bb1ac3bfb56243c8e9ea Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 14 Mar 2021 21:36:18 +0200 Subject: [PATCH 19/19] Bump badge --- README.md | 2 +- README.rst | 2 +- docs/source/index.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3c43b881..5205646d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![PyPi status](https://img.shields.io/pypi/status/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) [![Downloads](https://img.shields.io/pypi/dm/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) [![Supported python versions](https://img.shields.io/pypi/pyversions/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) -[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-5.0-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api) +[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-5.1-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api) [![Documentation Status](https://img.shields.io/readthedocs/aiogram?style=flat-square)](http://docs.aiogram.dev/en/latest/?badge=latest) [![Github issues](https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square)](https://github.com/aiogram/aiogram/issues) [![MIT License](https://img.shields.io/pypi/l/aiogram.svg?style=flat-square)](https://opensource.org/licenses/MIT) diff --git a/README.rst b/README.rst index 09072c81..3ec899c2 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-5.0-blue.svg?style=flat-square&logo=telegram +.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.1-blue.svg?style=flat-square&logo=telegram :target: https://core.telegram.org/bots/api :alt: Telegram Bot API diff --git a/docs/source/index.rst b/docs/source/index.rst index e08e8830..809e195e 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-5.0-blue.svg?style=flat-square&logo=telegram + .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.1-blue.svg?style=flat-square&logo=telegram :target: https://core.telegram.org/bots/api :alt: Telegram Bot API