diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index c4430ef6..d643b3f0 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ open_collective: aiogram +patreon: aiogram diff --git a/aiogram/__init__.py b/aiogram/__init__.py index ef832be9..590afc50 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -43,5 +43,5 @@ __all__ = ( 'utils', ) -__version__ = '2.12' +__version__ = '2.12.1' __api_version__ = '5.1' diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index 85be7fd0..4b6c4c0b 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -161,8 +161,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): files = {} prepare_file(payload, files, 'certificate', certificate) - result = await self.request(api.Methods.SET_WEBHOOK, payload, files) - return result + return await self.request(api.Methods.SET_WEBHOOK, payload, files) async def delete_webhook(self, drop_pending_updates: typing.Optional[base.Boolean] = None, @@ -181,8 +180,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.DELETE_WEBHOOK, payload) - return result + return await self.request(api.Methods.DELETE_WEBHOOK, payload) async def get_webhook_info(self) -> types.WebhookInfo: """ @@ -232,8 +230,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.LOG_OUT, payload) - return result + return await self.request(api.Methods.LOG_OUT, payload) @deprecated("This method will be renamed to `close` in aiogram v3.0") async def close_bot(self) -> base.Boolean: @@ -251,8 +248,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.CLOSE, payload) - return result + return await self.request(api.Methods.CLOSE, payload) async def send_message(self, chat_id: typing.Union[base.Integer, base.String], @@ -1506,8 +1502,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.SEND_CHAT_ACTION, payload) - return result + return await self.request(api.Methods.SEND_CHAT_ACTION, payload) async def get_user_profile_photos(self, user_id: base.Integer, offset: typing.Optional[base.Integer] = None, limit: typing.Optional[base.Integer] = None) -> types.UserProfilePhotos: @@ -1593,8 +1588,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): until_date = prepare_arg(until_date) payload = generate_payload(**locals()) - result = await self.request(api.Methods.KICK_CHAT_MEMBER, payload) - return result + return await self.request(api.Methods.KICK_CHAT_MEMBER, payload) async def unban_chat_member(self, chat_id: typing.Union[base.Integer, base.String], @@ -1627,8 +1621,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.UNBAN_CHAT_MEMBER, payload) - return result + return await self.request(api.Methods.UNBAN_CHAT_MEMBER, payload) async def restrict_chat_member(self, chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer, @@ -1683,8 +1676,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): f"passing regular argument {payload[permission]}", DeprecationWarning, stacklevel=2) - result = await self.request(api.Methods.RESTRICT_CHAT_MEMBER, payload) - return result + return await self.request(api.Methods.RESTRICT_CHAT_MEMBER, payload) async def promote_chat_member(self, chat_id: typing.Union[base.Integer, base.String], @@ -1756,8 +1748,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.PROMOTE_CHAT_MEMBER, payload) - return result + return await self.request(api.Methods.PROMOTE_CHAT_MEMBER, payload) async def set_chat_administrator_custom_title(self, chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer, custom_title: base.String) -> base.Boolean: @@ -1775,8 +1766,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE, payload) - return result + return await self.request(api.Methods.SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE, payload) async def set_chat_permissions(self, chat_id: typing.Union[base.Integer, base.String], permissions: types.ChatPermissions) -> base.Boolean: @@ -1794,8 +1784,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): permissions = prepare_arg(permissions) payload = generate_payload(**locals()) - result = await self.request(api.Methods.SET_CHAT_PERMISSIONS, payload) - return result + return await self.request(api.Methods.SET_CHAT_PERMISSIONS, payload) async def export_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String]) -> base.String: """ @@ -1811,8 +1800,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.EXPORT_CHAT_INVITE_LINK, payload) - return result + return await self.request(api.Methods.EXPORT_CHAT_INVITE_LINK, payload) async def create_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String], @@ -1846,8 +1834,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): expire_date = prepare_arg(expire_date) payload = generate_payload(**locals()) - result = await self.request(api.Methods.CREATE_CHAT_INVITE_LINK, payload) - return result + return await self.request(api.Methods.CREATE_CHAT_INVITE_LINK, payload) async def edit_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String], @@ -1883,8 +1870,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): expire_date = prepare_arg(expire_date) payload = generate_payload(**locals()) - result = await self.request(api.Methods.EDIT_CHAT_INVITE_LINK, payload) - return result + return await self.request(api.Methods.EDIT_CHAT_INVITE_LINK, payload) async def revoke_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String], @@ -1905,8 +1891,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.REVOKE_CHAT_INVITE_LINK, payload) - return result + return await self.request(api.Methods.REVOKE_CHAT_INVITE_LINK, payload) async def set_chat_photo(self, chat_id: typing.Union[base.Integer, base.String], photo: base.InputFile) -> base.Boolean: @@ -1931,8 +1916,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): files = {} prepare_file(payload, files, 'photo', photo) - result = await self.request(api.Methods.SET_CHAT_PHOTO, payload, files) - return result + return await self.request(api.Methods.SET_CHAT_PHOTO, payload, files) async def delete_chat_photo(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean: """ @@ -1951,8 +1935,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.DELETE_CHAT_PHOTO, payload) - return result + return await self.request(api.Methods.DELETE_CHAT_PHOTO, payload) async def set_chat_title(self, chat_id: typing.Union[base.Integer, base.String], title: base.String) -> base.Boolean: @@ -1974,8 +1957,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.SET_CHAT_TITLE, payload) - return result + return await self.request(api.Methods.SET_CHAT_TITLE, payload) async def set_chat_description(self, chat_id: typing.Union[base.Integer, base.String], description: typing.Optional[base.String] = None) -> base.Boolean: @@ -1994,8 +1976,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.SET_CHAT_DESCRIPTION, payload) - return result + return await self.request(api.Methods.SET_CHAT_DESCRIPTION, payload) async def pin_chat_message(self, chat_id: typing.Union[base.Integer, base.String], @@ -2027,8 +2008,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.PIN_CHAT_MESSAGE, payload) - return result + return await self.request(api.Methods.PIN_CHAT_MESSAGE, payload) async def unpin_chat_message(self, chat_id: typing.Union[base.Integer, base.String], @@ -2056,8 +2036,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.UNPIN_CHAT_MESSAGE, payload) - return result + return await self.request(api.Methods.UNPIN_CHAT_MESSAGE, payload) async def unpin_all_chat_messages(self, chat_id: typing.Union[base.Integer, base.String], @@ -2079,8 +2058,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.UNPIN_ALL_CHAT_MESSAGES, payload) - return result + return await self.request(api.Methods.UNPIN_ALL_CHAT_MESSAGES, payload) async def leave_chat(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean: """ @@ -2095,8 +2073,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.LEAVE_CHAT, payload) - return result + return await self.request(api.Methods.LEAVE_CHAT, payload) async def get_chat(self, chat_id: typing.Union[base.Integer, base.String]) -> types.Chat: """ @@ -2148,8 +2125,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.GET_CHAT_MEMBERS_COUNT, payload) - return result + return await self.request(api.Methods.GET_CHAT_MEMBERS_COUNT, payload) async def get_chat_member(self, chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer) -> types.ChatMember: @@ -2190,8 +2166,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.SET_CHAT_STICKER_SET, payload) - return result + return await self.request(api.Methods.SET_CHAT_STICKER_SET, payload) async def delete_chat_sticker_set(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean: """ @@ -2210,8 +2185,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.DELETE_CHAT_STICKER_SET, payload) - return result + return await self.request(api.Methods.DELETE_CHAT_STICKER_SET, payload) async def answer_callback_query(self, callback_query_id: base.String, text: typing.Optional[base.String] = None, @@ -2245,8 +2219,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.ANSWER_CALLBACK_QUERY, payload) - return result + return await self.request(api.Methods.ANSWER_CALLBACK_QUERY, payload) async def set_my_commands(self, commands: typing.List[types.BotCommand]) -> base.Boolean: """ @@ -2263,8 +2236,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): commands = prepare_arg(commands) payload = generate_payload(**locals()) - result = await self.request(api.Methods.SET_MY_COMMANDS, payload) - return result + return await self.request(api.Methods.SET_MY_COMMANDS, payload) async def get_my_commands(self) -> typing.List[types.BotCommand]: """ @@ -2510,8 +2482,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.DELETE_MESSAGE, payload) - return result + return await self.request(api.Methods.DELETE_MESSAGE, payload) # === Stickers === # https://core.telegram.org/bots/api#stickers @@ -2652,8 +2623,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): prepare_file(payload, files, 'png_sticker', png_sticker) prepare_file(payload, files, 'tgs_sticker', tgs_sticker) - result = await self.request(api.Methods.CREATE_NEW_STICKER_SET, payload, files) - return result + return await self.request(api.Methods.CREATE_NEW_STICKER_SET, payload, files) async def add_sticker_to_set(self, user_id: base.Integer, @@ -2698,8 +2668,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): prepare_file(payload, files, 'png_sticker', png_sticker) prepare_file(payload, files, 'tgs_sticker', tgs_sticker) - result = await self.request(api.Methods.ADD_STICKER_TO_SET, payload, files) - return result + return await self.request(api.Methods.ADD_STICKER_TO_SET, payload, files) async def set_sticker_position_in_set(self, sticker: base.String, position: base.Integer) -> base.Boolean: """ @@ -2715,9 +2684,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): :rtype: :obj:`base.Boolean` """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.SET_STICKER_POSITION_IN_SET, payload) - return result + return await self.request(api.Methods.SET_STICKER_POSITION_IN_SET, payload) async def delete_sticker_from_set(self, sticker: base.String) -> base.Boolean: """ @@ -2732,8 +2700,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.DELETE_STICKER_FROM_SET, payload) - return result + return await self.request(api.Methods.DELETE_STICKER_FROM_SET, payload) async def set_sticker_set_thumb(self, name: base.String, @@ -2765,8 +2732,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): files = {} prepare_file(payload, files, 'thumb', thumb) - result = await self.request(api.Methods.SET_STICKER_SET_THUMB, payload, files) - return result + return await self.request(api.Methods.SET_STICKER_SET_THUMB, payload, files) async def answer_inline_query(self, inline_query_id: base.String, results: typing.List[types.InlineQueryResult], @@ -2809,8 +2775,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): results = prepare_arg(results) payload = generate_payload(**locals()) - result = await self.request(api.Methods.ANSWER_INLINE_QUERY, payload) - return result + return await self.request(api.Methods.ANSWER_INLINE_QUERY, payload) # === Payments === # https://core.telegram.org/bots/api#payments @@ -2958,8 +2923,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): for shipping_option in shipping_options]) payload = generate_payload(**locals()) - result = await self.request(api.Methods.ANSWER_SHIPPING_QUERY, payload) - return result + return await self.request(api.Methods.ANSWER_SHIPPING_QUERY, payload) async def answer_pre_checkout_query(self, pre_checkout_query_id: base.String, ok: base.Boolean, error_message: typing.Optional[base.String] = None) -> base.Boolean: @@ -2986,8 +2950,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): """ payload = generate_payload(**locals()) - result = await self.request(api.Methods.ANSWER_PRE_CHECKOUT_QUERY, payload) - return result + return await self.request(api.Methods.ANSWER_PRE_CHECKOUT_QUERY, payload) # === Games === # https://core.telegram.org/bots/api#games @@ -3018,8 +2981,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin): errors = prepare_arg(errors) payload = generate_payload(**locals()) - result = await self.request(api.Methods.SET_PASSPORT_DATA_ERRORS, payload) - return result + return await self.request(api.Methods.SET_PASSPORT_DATA_ERRORS, payload) # === Games === # https://core.telegram.org/bots/api#games diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index a2736cf7..d471fe86 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -11,7 +11,7 @@ from aiohttp.helpers import sentinel from aiogram.utils.deprecated import renamed_argument from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \ RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter, IsReplyFilter, ForwardedMessageFilter, \ - IsSenderContact, ChatTypeFilter, AbstractFilter + IsSenderContact, ChatTypeFilter, MediaGroupFilter, AbstractFilter from .handler import Handler from .middlewares import MiddlewareManager from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \ @@ -204,6 +204,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin): self.my_chat_member_handlers, self.chat_member_handlers ]) + filters_factory.bind(MediaGroupFilter, event_handlers=[ + self.message_handlers, + self.edited_channel_post_handlers, + self.channel_post_handlers, + self.edited_channel_post_handlers + ]) def __del__(self): self.stop_polling() diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py index d64a2667..d07d953b 100644 --- a/aiogram/dispatcher/filters/__init__.py +++ b/aiogram/dispatcher/filters/__init__.py @@ -1,7 +1,7 @@ from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \ ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, \ Text, IDFilter, AdminFilter, IsReplyFilter, IsSenderContact, ForwardedMessageFilter, \ - ChatTypeFilter + ChatTypeFilter, MediaGroupFilter from .factory import FiltersFactory from .filters import AbstractFilter, BoundFilter, Filter, FilterNotPassed, FilterRecord, execute_filter, \ check_filters, get_filter_spec, get_filters_spec @@ -25,6 +25,7 @@ __all__ = ( 'IsSenderContact', 'ForwardedMessageFilter', 'ChatTypeFilter', + 'MediaGroupFilter', 'FiltersFactory', 'AbstractFilter', 'BoundFilter', diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index 762c8505..c32c53be 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -738,3 +738,20 @@ class ChatTypeFilter(BoundFilter): return False return obj.type in self.chat_type + + +class MediaGroupFilter(BoundFilter): + """ + Check if message is part of a media group. + + `is_media_group=True` - the message is part of a media group + `is_media_group=False` - the message is NOT part of a media group + """ + + key = "is_media_group" + + def __init__(self, is_media_group: bool): + self.is_media_group = is_media_group + + async def check(self, message: types.Message) -> bool: + return bool(getattr(message, "media_group_id")) is self.is_media_group diff --git a/aiogram/dispatcher/handler.py b/aiogram/dispatcher/handler.py index 38219012..10a94924 100644 --- a/aiogram/dispatcher/handler.py +++ b/aiogram/dispatcher/handler.py @@ -25,8 +25,7 @@ class CancelHandler(Exception): def _get_spec(func: callable): while hasattr(func, '__wrapped__'): # Try to resolve decorated callbacks func = func.__wrapped__ - spec = inspect.getfullargspec(func) - return spec + return inspect.getfullargspec(func) def _check_spec(spec: inspect.FullArgSpec, kwargs: dict): diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index b6280ed1..52191870 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -116,8 +116,7 @@ class WebhookRequestHandler(web.View): :return: :class:`aiogram.types.Update` """ data = await self.request.json() - update = types.Update(**data) - return update + return types.Update(**data) async def post(self): """ diff --git a/aiogram/types/base.py b/aiogram/types/base.py index 373f47be..0ed8579c 100644 --- a/aiogram/types/base.py +++ b/aiogram/types/base.py @@ -1,6 +1,7 @@ from __future__ import annotations import io +import logging import typing from typing import TypeVar @@ -26,6 +27,9 @@ Float = TypeVar('Float', bound=float) Boolean = TypeVar('Boolean', bound=bool) T = TypeVar('T') +# Main aiogram logger +log = logging.getLogger('aiogram') + class MetaTelegramObject(type): """ @@ -225,7 +229,9 @@ 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) + + # Log warning when Telegram silently adds new Fields + log.warning("Field '%s' doesn't exist in %s", key, self.__class__) def __contains__(self, item: str) -> bool: """ diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index b9e03983..3b0a7b9d 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -35,6 +35,7 @@ class Chat(base.TelegramObject): pinned_message: 'Message' = fields.Field(base='Message') permissions: ChatPermissions = fields.Field(base=ChatPermissions) slow_mode_delay: base.Integer = fields.Field() + message_auto_delete_time: base.Integer = fields.Field() sticker_set_name: base.String = fields.Field() can_set_sticker_set: base.Boolean = fields.Field() linked_chat_id: base.Integer = fields.Field() @@ -607,6 +608,15 @@ class Chat(base.TelegramObject): invite_link=invite_link, ) + async def delete_message(self, + message_id: base.Integer, + ) -> base.Boolean: + """ Shortcut for deleteMessage method. """ + return await self.bot.delete_message( + chat_id=self.id, + message_id=message_id, + ) + def __int__(self): return self.id diff --git a/aiogram/types/chat_member_updated.py b/aiogram/types/chat_member_updated.py index 7c6a124f..67c75616 100644 --- a/aiogram/types/chat_member_updated.py +++ b/aiogram/types/chat_member_updated.py @@ -15,7 +15,7 @@ class ChatMemberUpdated(base.TelegramObject): https://core.telegram.org/bots/api#chatmemberupdated """ chat: Chat = fields.Field(base=Chat) - from_user: User = fields.Field(base=User) + from_user: User = fields.Field(alias="from", base=User) date: datetime.datetime = fields.DateTimeField() old_chat_member: ChatMember = fields.Field(base=ChatMember) new_chat_member: ChatMember = fields.Field(base=ChatMember) diff --git a/aiogram/types/document.py b/aiogram/types/document.py index e15b745d..c3d19fa9 100644 --- a/aiogram/types/document.py +++ b/aiogram/types/document.py @@ -2,6 +2,7 @@ from . import base from . import fields from . import mixins from .photo_size import PhotoSize +from ..utils import helper class Document(base.TelegramObject, mixins.Downloadable): @@ -16,3 +17,34 @@ class Document(base.TelegramObject, mixins.Downloadable): file_name: base.String = fields.Field() mime_type: base.String = fields.Field() file_size: base.Integer = fields.Field() + + @property + def mime_base(self) -> str: + base_type, _, _ = self.mime_type.partition('/') + return base_type + + @property + def mime_subtype(self) -> str: + _, _, subtype = self.mime_type.partition('/') + return subtype + + +class MimeBase(helper.Helper): + """ + List of mime base types registered in IANA + + https://www.iana.org/assignments/media-types/media-types.xhtml + """ + + mode = helper.HelperMode.lowercase + + APPLICATION = helper.Item() # application + AUDIO = helper.Item() # audio + EXAMPLE = helper.Item() # example + FONT = helper.Item() # font + IMAGE = helper.Item() # image + MESSAGE = helper.Item() # message + MODEL = helper.Item() # model + MULTIPART = helper.Item() # multipart + TEXT = helper.Item() # text + VIDEO = helper.Item() # video diff --git a/aiogram/utils/text_decorations.py b/aiogram/utils/text_decorations.py index 4de8d69a..09484e3c 100644 --- a/aiogram/utils/text_decorations.py +++ b/aiogram/utils/text_decorations.py @@ -55,12 +55,11 @@ class TextDecoration(ABC): :param entities: Array of MessageEntities :return: """ - result = "".join( + return "".join( self._unparse_entities( self._add_surrogates(text), sorted(entities, key=lambda item: item.offset) if entities else [] ) ) - return result def _unparse_entities( self, diff --git a/docs/source/dispatcher/filters.rst b/docs/source/dispatcher/filters.rst index b8f4962e..a47e396e 100644 --- a/docs/source/dispatcher/filters.rst +++ b/docs/source/dispatcher/filters.rst @@ -155,6 +155,14 @@ ChatTypeFilter .. autoclass:: aiogram.dispatcher.filters.ChatTypeFilter :members: :show-inheritance: + + +MediaGroupFilter +------------- + +.. autoclass:: aiogram.dispatcher.filters.MediaGroupFilter + :members: + :show-inheritance: Making own filters (Custom filters) diff --git a/examples/chat_type_filter.py b/examples/chat_type_filter.py index e57f8825..9db13cb8 100644 --- a/examples/chat_type_filter.py +++ b/examples/chat_type_filter.py @@ -19,12 +19,12 @@ bot = Bot(token=API_TOKEN) dp = Dispatcher(bot) -@dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.CHANNEL]) +@dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.SUPERGROUP]) 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 supergroup """ - await message.reply("Hi!\nI'm hearing your messages in private chats and channels") + await message.reply("Hi!\nI'm hearing your messages in private chats and supergroups") # propagate message to the next handler raise SkipHandler