diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py index 554393cf..a5df27bd 100644 --- a/aiogram/client/bot.py +++ b/aiogram/client/bot.py @@ -1565,7 +1565,7 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#unbanchatmember - :param chat_id: Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@username`) + :param chat_id: Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@channelusername`) :param user_id: Unique identifier of the target user :param only_if_banned: Do nothing if the user is not banned :param request_timeout: Request timeout @@ -2569,7 +2569,7 @@ class Bot(ContextInstanceMixin["Bot"]): request_timeout: Optional[int] = None, ) -> Message: """ - Use this method to send static .WEBP or `animated `_ .TGS stickers. On success, the sent :class:`aiogram.types.message.Message` is returned. + Use this method to send static .WEBP, `animated `_ .TGS, or `video `_ .WEBM stickers. On success, the sent :class:`aiogram.types.message.Message` is returned. Source: https://core.telegram.org/bots/api#sendsticker @@ -2643,12 +2643,13 @@ class Bot(ContextInstanceMixin["Bot"]): emojis: str, png_sticker: Optional[Union[InputFile, str]] = None, tgs_sticker: Optional[InputFile] = None, + webm_sticker: Optional[InputFile] = None, contains_masks: Optional[bool] = None, mask_position: Optional[MaskPosition] = None, request_timeout: Optional[int] = None, ) -> bool: """ - Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Returns :code:`True` on success. + Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#createnewstickerset @@ -2657,7 +2658,8 @@ class Bot(ContextInstanceMixin["Bot"]): :param title: Sticker set title, 1-64 characters :param emojis: One or more emoji corresponding to the sticker :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » ` - :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for technical requirements + :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements + :param webm_sticker: **WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements :param contains_masks: Pass :code:`True`, if a set of mask stickers should be created :param mask_position: A JSON-serialized object for position where the mask should be placed on faces :param request_timeout: Request timeout @@ -2670,6 +2672,7 @@ class Bot(ContextInstanceMixin["Bot"]): emojis=emojis, png_sticker=png_sticker, tgs_sticker=tgs_sticker, + webm_sticker=webm_sticker, contains_masks=contains_masks, mask_position=mask_position, ) @@ -2682,11 +2685,12 @@ class Bot(ContextInstanceMixin["Bot"]): emojis: str, png_sticker: Optional[Union[InputFile, str]] = None, tgs_sticker: Optional[InputFile] = None, + webm_sticker: Optional[InputFile] = None, mask_position: Optional[MaskPosition] = None, request_timeout: Optional[int] = None, ) -> bool: """ - Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success. + Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#addstickertoset @@ -2694,7 +2698,8 @@ class Bot(ContextInstanceMixin["Bot"]): :param name: Sticker set name :param emojis: One or more emoji corresponding to the sticker :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » ` - :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for technical requirements + :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements + :param webm_sticker: **WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements :param mask_position: A JSON-serialized object for position where the mask should be placed on faces :param request_timeout: Request timeout :return: Returns True on success. @@ -2705,6 +2710,7 @@ class Bot(ContextInstanceMixin["Bot"]): emojis=emojis, png_sticker=png_sticker, tgs_sticker=tgs_sticker, + webm_sticker=webm_sticker, mask_position=mask_position, ) return await self(call, request_timeout=request_timeout) @@ -2758,13 +2764,13 @@ class Bot(ContextInstanceMixin["Bot"]): request_timeout: Optional[int] = None, ) -> bool: """ - Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Returns :code:`True` on success. + Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Video thumbnails can be set only for video sticker sets only. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#setstickersetthumb :param name: Sticker set name :param user_id: User identifier of the sticker set owner - :param thumb: A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for animated sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » `. Animated sticker set thumbnail can't be uploaded via HTTP URL. + :param thumb: A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for animated sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » `. Animated sticker set thumbnails can't be uploaded via HTTP URL. :param request_timeout: Request timeout :return: Returns True on success. """ diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py index 74f175ac..c699c5d5 100644 --- a/aiogram/dispatcher/filters/__init__.py +++ b/aiogram/dispatcher/filters/__init__.py @@ -1,6 +1,21 @@ from typing import Dict, Tuple, Type from .base import BaseFilter +from .chat_member import ( + ADMINISTRATOR, + CREATOR, + IS_ADMIN, + IS_MEMBER, + IS_NOT_MEMBER, + JOIN_TRANSITION, + KICKED, + LEAVE_TRANSITION, + LEFT, + MEMBER, + PROMOTED_TRANSITION, + RESTRICTED, + ChatMemberUpdatedStatus, +) from .command import Command, CommandObject from .content_types import ContentTypesFilter from .exception import ExceptionMessageFilter, ExceptionTypeFilter @@ -19,6 +34,19 @@ __all__ = ( "ExceptionTypeFilter", "StateFilter", "MagicData", + "ChatMemberUpdatedStatus", + "CREATOR", + "ADMINISTRATOR", + "MEMBER", + "RESTRICTED", + "LEFT", + "KICKED", + "IS_MEMBER", + "IS_ADMIN", + "PROMOTED_TRANSITION", + "IS_NOT_MEMBER", + "JOIN_TRANSITION", + "LEAVE_TRANSITION", ) _ALL_EVENTS_FILTERS: Tuple[Type[BaseFilter], ...] = (MagicData,) @@ -84,10 +112,12 @@ BUILTIN_FILTERS: Dict[str, Tuple[Type[BaseFilter], ...]] = { "my_chat_member": ( *_ALL_EVENTS_FILTERS, *_TELEGRAM_EVENTS_FILTERS, + ChatMemberUpdatedStatus, ), "chat_member": ( *_ALL_EVENTS_FILTERS, *_TELEGRAM_EVENTS_FILTERS, + ChatMemberUpdatedStatus, ), "chat_join_request": ( *_ALL_EVENTS_FILTERS, diff --git a/aiogram/dispatcher/filters/chat_member.py b/aiogram/dispatcher/filters/chat_member.py new file mode 100644 index 00000000..30fe5cdc --- /dev/null +++ b/aiogram/dispatcher/filters/chat_member.py @@ -0,0 +1,155 @@ +from typing import Any, Dict, Optional, TypeVar, Union + +from aiogram.dispatcher.filters import BaseFilter +from aiogram.types import ChatMember, ChatMemberUpdated + +MarkerT = TypeVar("MarkerT", bound="_MemberStatusMarker") +MarkerGroupT = TypeVar("MarkerGroupT", bound="_MemberStatusGroupMarker") +TransitionT = TypeVar("TransitionT", bound="_MemberStatusTransition") + + +class _MemberStatusMarker: + def __init__(self, name: str, *, is_member: Optional[bool] = None) -> None: + self.name = name + self.is_member = is_member + + def __str__(self) -> str: + result = self.name.upper() + if self.is_member is not None: + result = ("+" if self.is_member else "-") + result + return result + + def __pos__(self: MarkerT) -> MarkerT: + return type(self)(name=self.name, is_member=True) + + def __neg__(self: MarkerT) -> MarkerT: + return type(self)(name=self.name, is_member=False) + + def __or__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusGroupMarker": + if isinstance(other, _MemberStatusMarker): + return _MemberStatusGroupMarker(self, other) + if isinstance(other, _MemberStatusGroupMarker): + return other | self + raise TypeError("can't be combined") + + def __rshift__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusTransition": + old = _MemberStatusGroupMarker(self) + if isinstance(other, _MemberStatusMarker): + return _MemberStatusTransition(old=old, new=_MemberStatusGroupMarker(other)) + if isinstance(other, _MemberStatusGroupMarker): + return _MemberStatusTransition(old=old, new=other) + raise TypeError("can't be combined") + + def __lshift__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusTransition": + new = _MemberStatusGroupMarker(self) + if isinstance(other, _MemberStatusMarker): + return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=new) + if isinstance(other, _MemberStatusGroupMarker): + return _MemberStatusTransition(old=other, new=new) + raise TypeError("can't be combined") + + def __hash__(self) -> int: + return hash((self.name, self.is_member)) + + def check_member(self, member: ChatMember) -> bool: + if self.is_member is not None and member.is_member != self.is_member: + return False + return self.name == member.status + + +class _MemberStatusGroupMarker: + def __init__(self, *statuses: _MemberStatusMarker) -> None: + self.statuses = set(statuses) + + def __or__( + self: MarkerGroupT, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> MarkerGroupT: + if isinstance(other, _MemberStatusMarker): + return type(self)(*self.statuses, other) + elif isinstance(other, _MemberStatusGroupMarker): + return type(self)(*self.statuses, *other.statuses) + raise TypeError("can't be combined") + + def __rshift__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusTransition": + if isinstance(other, _MemberStatusMarker): + return _MemberStatusTransition(old=self, new=_MemberStatusGroupMarker(other)) + if isinstance(other, _MemberStatusGroupMarker): + return _MemberStatusTransition(old=self, new=other) + raise TypeError("can't be combined") + + def __lshift__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusTransition": + if isinstance(other, _MemberStatusMarker): + return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=self) + if isinstance(other, _MemberStatusGroupMarker): + return _MemberStatusTransition(old=other, new=self) + raise TypeError("can't be combined") + + def __str__(self) -> str: + result = " | ".join(map(str, sorted(self.statuses, key=lambda s: (s.name, s.is_member)))) + if len(self.statuses) != 1: + return f"({result})" + return result + + def check_member(self, member: ChatMember) -> bool: + for status in self.statuses: + if status.check_member(member): + return True + return False + + +class _MemberStatusTransition: + def __init__(self, *, old: _MemberStatusGroupMarker, new: _MemberStatusGroupMarker) -> None: + self.old = old + self.new = new + + def __str__(self) -> str: + return f"{self.old} >> {self.new}" + + def __invert__(self: TransitionT) -> TransitionT: + return type(self)(old=self.new, new=self.old) + + +CREATOR = _MemberStatusMarker("creator") +ADMINISTRATOR = _MemberStatusMarker("administrator") +MEMBER = _MemberStatusMarker("member") +RESTRICTED = _MemberStatusMarker("restricted") +LEFT = _MemberStatusMarker("left") +KICKED = _MemberStatusMarker("kicked") + +IS_MEMBER = CREATOR | ADMINISTRATOR | MEMBER | +RESTRICTED +IS_ADMIN = CREATOR | ADMINISTRATOR +PROMOTED_TRANSITION = (MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR +IS_NOT_MEMBER = LEFT | KICKED | -RESTRICTED + +JOIN_TRANSITION = IS_NOT_MEMBER >> IS_MEMBER +LEAVE_TRANSITION = ~JOIN_TRANSITION + + +class ChatMemberUpdatedStatus(BaseFilter): + member_status_changed: Union[ + _MemberStatusMarker, _MemberStatusGroupMarker, _MemberStatusTransition + ] + + class Config: + arbitrary_types_allowed = True + + async def __call__(self, member_updated: ChatMemberUpdated) -> Union[bool, Dict[str, Any]]: + old = member_updated.old_chat_member + new = member_updated.new_chat_member + rule = self.member_status_changed + + if isinstance(rule, (_MemberStatusMarker, _MemberStatusGroupMarker)): + return rule.check_member(new) + if isinstance(rule, _MemberStatusTransition): + return rule.old.check_member(old) and rule.new.check_member(new) + return False diff --git a/aiogram/dispatcher/fsm/storage/redis.py b/aiogram/dispatcher/fsm/storage/redis.py index 58ce50f6..ce215d8e 100644 --- a/aiogram/dispatcher/fsm/storage/redis.py +++ b/aiogram/dispatcher/fsm/storage/redis.py @@ -127,7 +127,7 @@ class RedisStorage(BaseStorage): redis = Redis(connection_pool=pool) return cls(redis=redis, **kwargs) - def create_isolation(self, **kwargs) -> "RedisEventIsolation": + def create_isolation(self, **kwargs: Any) -> "RedisEventIsolation": return RedisEventIsolation(redis=self.redis, key_builder=self.key_builder, **kwargs) async def close(self) -> None: diff --git a/aiogram/methods/add_sticker_to_set.py b/aiogram/methods/add_sticker_to_set.py index 43499324..7b676674 100644 --- a/aiogram/methods/add_sticker_to_set.py +++ b/aiogram/methods/add_sticker_to_set.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class AddStickerToSet(TelegramMethod[bool]): """ - Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success. + Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#addstickertoset """ @@ -27,15 +27,18 @@ class AddStickerToSet(TelegramMethod[bool]): png_sticker: Optional[Union[InputFile, str]] = None """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » `""" tgs_sticker: Optional[InputFile] = None - """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for technical requirements""" + """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements""" + webm_sticker: Optional[InputFile] = None + """**WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements""" mask_position: Optional[MaskPosition] = None """A JSON-serialized object for position where the mask should be placed on faces""" def build_request(self, bot: Bot) -> Request: - data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker"}) + data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker", "webm_sticker"}) files: Dict[str, InputFile] = {} prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker) prepare_file(data=data, files=files, name="tgs_sticker", value=self.tgs_sticker) + prepare_file(data=data, files=files, name="webm_sticker", value=self.webm_sticker) return Request(method="addStickerToSet", data=data, files=files) diff --git a/aiogram/methods/create_new_sticker_set.py b/aiogram/methods/create_new_sticker_set.py index baa29ad8..5c807963 100644 --- a/aiogram/methods/create_new_sticker_set.py +++ b/aiogram/methods/create_new_sticker_set.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class CreateNewStickerSet(TelegramMethod[bool]): """ - Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Returns :code:`True` on success. + Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#createnewstickerset """ @@ -29,17 +29,20 @@ class CreateNewStickerSet(TelegramMethod[bool]): png_sticker: Optional[Union[InputFile, str]] = None """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » `""" tgs_sticker: Optional[InputFile] = None - """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for technical requirements""" + """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements""" + webm_sticker: Optional[InputFile] = None + """**WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements""" contains_masks: Optional[bool] = None """Pass :code:`True`, if a set of mask stickers should be created""" mask_position: Optional[MaskPosition] = None """A JSON-serialized object for position where the mask should be placed on faces""" def build_request(self, bot: Bot) -> Request: - data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker"}) + data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker", "webm_sticker"}) files: Dict[str, InputFile] = {} prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker) prepare_file(data=data, files=files, name="tgs_sticker", value=self.tgs_sticker) + prepare_file(data=data, files=files, name="webm_sticker", value=self.webm_sticker) return Request(method="createNewStickerSet", data=data, files=files) diff --git a/aiogram/methods/send_sticker.py b/aiogram/methods/send_sticker.py index abbe3773..573040e1 100644 --- a/aiogram/methods/send_sticker.py +++ b/aiogram/methods/send_sticker.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: class SendSticker(TelegramMethod[Message]): """ - Use this method to send static .WEBP or `animated `_ .TGS stickers. On success, the sent :class:`aiogram.types.message.Message` is returned. + Use this method to send static .WEBP, `animated `_ .TGS, or `video `_ .WEBM stickers. On success, the sent :class:`aiogram.types.message.Message` is returned. Source: https://core.telegram.org/bots/api#sendsticker """ diff --git a/aiogram/methods/set_sticker_set_thumb.py b/aiogram/methods/set_sticker_set_thumb.py index 5ab66cd5..49ef7971 100644 --- a/aiogram/methods/set_sticker_set_thumb.py +++ b/aiogram/methods/set_sticker_set_thumb.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class SetStickerSetThumb(TelegramMethod[bool]): """ - Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Returns :code:`True` on success. + Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Video thumbnails can be set only for video sticker sets only. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#setstickersetthumb """ @@ -23,7 +23,7 @@ class SetStickerSetThumb(TelegramMethod[bool]): user_id: int """User identifier of the sticker set owner""" thumb: Optional[Union[InputFile, str]] = None - """A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for animated sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » `. Animated sticker set thumbnail can't be uploaded via HTTP URL.""" + """A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for animated sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » `. Animated sticker set thumbnails can't be uploaded via HTTP URL.""" def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"thumb"}) diff --git a/aiogram/methods/unban_chat_member.py b/aiogram/methods/unban_chat_member.py index e9938a84..3fce083b 100644 --- a/aiogram/methods/unban_chat_member.py +++ b/aiogram/methods/unban_chat_member.py @@ -18,7 +18,7 @@ class UnbanChatMember(TelegramMethod[bool]): __returning__ = bool chat_id: Union[int, str] - """Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@username`)""" + """Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@channelusername`)""" user_id: int """Unique identifier of the target user""" only_if_banned: Optional[bool] = None diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py index 018bebda..b3d1419c 100644 --- a/aiogram/types/chat_member.py +++ b/aiogram/types/chat_member.py @@ -1,7 +1,13 @@ from __future__ import annotations +import datetime +from typing import TYPE_CHECKING, Optional, Union + from .base import TelegramObject +if TYPE_CHECKING: + from .user import User + class ChatMember(TelegramObject): """ @@ -16,3 +22,48 @@ class ChatMember(TelegramObject): Source: https://core.telegram.org/bots/api#chatmember """ + + status: str + """...""" + user: Optional[User] = None + """*Optional*. Information about the user""" + is_anonymous: Optional[bool] = None + """*Optional*. :code:`True`, if the user's presence in the chat is hidden""" + custom_title: Optional[str] = None + """*Optional*. Custom title for this user""" + can_be_edited: Optional[bool] = None + """*Optional*. :code:`True`, if the bot is allowed to edit administrator privileges of that user""" + can_manage_chat: Optional[bool] = None + """*Optional*. :code:`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""" + can_delete_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can delete messages of other users""" + can_manage_voice_chats: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can manage voice chats""" + can_restrict_members: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can restrict, ban or unban chat members""" + can_promote_members: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can add new administrators with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by the user)""" + can_change_info: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to change the chat title, photo and other settings""" + can_invite_users: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to invite new users to the chat""" + can_post_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can post in the channel; channels only""" + can_edit_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can edit messages of other users and can pin messages; channels only""" + can_pin_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to pin messages; groups and supergroups only""" + is_member: Optional[bool] = None + """*Optional*. :code:`True`, if the user is a member of the chat at the moment of the request""" + can_send_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to send text messages, contacts, locations and venues""" + can_send_media_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes""" + can_send_polls: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to send polls""" + can_send_other_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to send animations, games, stickers and use inline bots""" + can_add_web_page_previews: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to add web page previews to their messages""" + until_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None + """*Optional*. Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever""" diff --git a/aiogram/utils/chat_action.py b/aiogram/utils/chat_action.py index b67f0a32..fe96412d 100644 --- a/aiogram/utils/chat_action.py +++ b/aiogram/utils/chat_action.py @@ -37,6 +37,10 @@ class ChatActionSender: self._running = False self._lock = Lock() + async def _wait(self, interval: float) -> None: + with suppress(asyncio.TimeoutError): + await asyncio.wait_for(self._close_event.wait(), interval) + async def _worker(self) -> None: logger.debug( "Started chat action %r sender in chat_id=%s via bot id=%d", @@ -46,7 +50,7 @@ class ChatActionSender: ) try: counter = 0 - await asyncio.sleep(self.initial_sleep) + await self._wait(self.initial_sleep) while not self._close_event.is_set(): start = time.monotonic() logger.debug( @@ -60,8 +64,7 @@ class ChatActionSender: counter += 1 interval = self.interval - (time.monotonic() - start) - with suppress(asyncio.TimeoutError): - await asyncio.wait_for(self._close_event.wait(), interval) + await self._wait(interval) finally: logger.debug( "Finished chat action %r sender in chat_id=%s via bot id=%d",