Merge branch 'dev-2.x'

This commit is contained in:
Alex Root Junior 2021-03-22 22:02:09 +02:00
commit 58c7da9dc2
15 changed files with 130 additions and 90 deletions

1
.github/FUNDING.yml vendored
View file

@ -1 +1,2 @@
open_collective: aiogram
patreon: aiogram

View file

@ -43,5 +43,5 @@ __all__ = (
'utils',
)
__version__ = '2.12'
__version__ = '2.12.1'
__api_version__ = '5.1'

View file

@ -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

View file

@ -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()

View file

@ -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',

View file

@ -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

View file

@ -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):

View file

@ -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):
"""

View file

@ -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:
"""

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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,

View file

@ -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)

View file

@ -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