From 24fb07d3fec290ae4bec37553e82e8ae15c901fb Mon Sep 17 00:00:00 2001 From: Oleg A Date: Sun, 14 Mar 2021 19:05:17 +0300 Subject: [PATCH] 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