Merge branch 'dev-2.x'

This commit is contained in:
Alex Root Junior 2021-03-14 21:36:38 +02:00
commit 45480890ae
28 changed files with 700 additions and 88 deletions

View file

@ -6,7 +6,7 @@
[![PyPi status](https://img.shields.io/pypi/status/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) [![PyPi status](https://img.shields.io/pypi/status/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
[![Downloads](https://img.shields.io/pypi/dm/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) [![Downloads](https://img.shields.io/pypi/dm/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
[![Supported python versions](https://img.shields.io/pypi/pyversions/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram) [![Supported python versions](https://img.shields.io/pypi/pyversions/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-5.0-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api) [![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-5.1-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api)
[![Documentation Status](https://img.shields.io/readthedocs/aiogram?style=flat-square)](http://docs.aiogram.dev/en/latest/?badge=latest) [![Documentation Status](https://img.shields.io/readthedocs/aiogram?style=flat-square)](http://docs.aiogram.dev/en/latest/?badge=latest)
[![Github issues](https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square)](https://github.com/aiogram/aiogram/issues) [![Github issues](https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square)](https://github.com/aiogram/aiogram/issues)
[![MIT License](https://img.shields.io/pypi/l/aiogram.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![MIT License](https://img.shields.io/pypi/l/aiogram.svg?style=flat-square)](https://opensource.org/licenses/MIT)

View file

@ -21,7 +21,7 @@ AIOGramBot
:target: https://pypi.python.org/pypi/aiogram :target: https://pypi.python.org/pypi/aiogram
:alt: Supported python versions :alt: Supported python versions
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.0-blue.svg?style=flat-square&logo=telegram .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.1-blue.svg?style=flat-square&logo=telegram
:target: https://core.telegram.org/bots/api :target: https://core.telegram.org/bots/api
:alt: Telegram Bot API :alt: Telegram Bot API

View file

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

View file

@ -189,7 +189,7 @@ class Methods(Helper):
""" """
Helper for Telegram API Methods listed on https://core.telegram.org/bots/api 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 mode = HelperMode.lowerCamelCase
@ -231,6 +231,9 @@ class Methods(Helper):
SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE = Item() # setChatAdministratorCustomTitle SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE = Item() # setChatAdministratorCustomTitle
SET_CHAT_PERMISSIONS = Item() # setChatPermissions SET_CHAT_PERMISSIONS = Item() # setChatPermissions
EXPORT_CHAT_INVITE_LINK = Item() # exportChatInviteLink 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 SET_CHAT_PHOTO = Item() # setChatPhoto
DELETE_CHAT_PHOTO = Item() # deleteChatPhoto DELETE_CHAT_PHOTO = Item() # deleteChatPhoto
SET_CHAT_TITLE = Item() # setChatTitle SET_CHAT_TITLE = Item() # setChatTitle

View file

@ -1550,28 +1550,43 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_FILE, payload) result = await self.request(api.Methods.GET_FILE, payload)
return types.File(**result) return types.File(**result)
async def kick_chat_member(self, chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer, async def kick_chat_member(self,
until_date: typing.Union[ chat_id: typing.Union[base.Integer, base.String],
base.Integer, datetime.datetime, datetime.timedelta, None] = None) -> base.Boolean: 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. 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 In the case of supergroups and channels, the user will not be able to return
on their own using invite links, etc., unless unbanned first. 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. 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.
Source: https://core.telegram.org/bots/api#kickchatmember 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]` :type chat_id: :obj:`typing.Union[base.Integer, base.String]`
:param user_id: Unique identifier of the target user :param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer` :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 :return: Returns True on success
:rtype: :obj:`base.Boolean` :rtype: :obj:`base.Boolean`
""" """
@ -1675,10 +1690,12 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
chat_id: typing.Union[base.Integer, base.String], chat_id: typing.Union[base.Integer, base.String],
user_id: base.Integer, user_id: base.Integer,
is_anonymous: typing.Optional[base.Boolean] = None, is_anonymous: typing.Optional[base.Boolean] = None,
can_manage_chat: typing.Optional[base.Boolean] = None,
can_change_info: typing.Optional[base.Boolean] = None, can_change_info: typing.Optional[base.Boolean] = None,
can_post_messages: typing.Optional[base.Boolean] = None, can_post_messages: typing.Optional[base.Boolean] = None,
can_edit_messages: typing.Optional[base.Boolean] = None, can_edit_messages: typing.Optional[base.Boolean] = None,
can_delete_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_invite_users: typing.Optional[base.Boolean] = None,
can_restrict_members: typing.Optional[base.Boolean] = None, can_restrict_members: typing.Optional[base.Boolean] = None,
can_pin_messages: 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 :param is_anonymous: Pass True, if the administrator's presence in the chat is hidden
:type is_anonymous: :obj:`typing.Optional[base.Boolean]` :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 :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]` :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 :param can_delete_messages: Pass True, if the administrator can delete messages of other users
:type can_delete_messages: :obj:`typing.Optional[base.Boolean]` :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 :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]` :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) result = await self.request(api.Methods.EXPORT_CHAT_INVITE_LINK, payload)
return result 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] = None,
member_limit: typing.Optional[base.Integer] = None,
) -> 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] = None,
member_limit: typing.Optional[base.Integer] = None,
) -> 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], async def set_chat_photo(self, chat_id: typing.Union[base.Integer, base.String],
photo: base.InputFile) -> base.Boolean: photo: base.InputFile) -> base.Boolean:
""" """

View file

@ -160,6 +160,26 @@ class LoggingMiddleware(BaseMiddleware):
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} poll answer [ID:{poll_answer.poll_id}] " self.logger.debug(f"{HANDLED_STR[bool(len(results))]} poll answer [ID:{poll_answer.poll_id}] "
f"from user [ID:{poll_answer.user.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): class LoggingFilter(logging.Filter):
""" """

View file

@ -78,6 +78,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query') self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query')
self.poll_handlers = Handler(self, middleware_key='poll') self.poll_handlers = Handler(self, middleware_key='poll')
self.poll_answer_handlers = Handler(self, middleware_key='poll_answer') 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.errors_handlers = Handler(self, once=False, middleware_key='error')
self.middleware = MiddlewareManager(self) self.middleware = MiddlewareManager(self)
@ -163,6 +165,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self.edited_channel_post_handlers, self.edited_channel_post_handlers,
self.callback_query_handlers, self.callback_query_handlers,
self.inline_query_handlers, self.inline_query_handlers,
self.chat_member_handlers,
]) ])
filters_factory.bind(IDFilter, event_handlers=[ filters_factory.bind(IDFilter, event_handlers=[
self.message_handlers, self.message_handlers,
@ -171,6 +174,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self.edited_channel_post_handlers, self.edited_channel_post_handlers,
self.callback_query_handlers, self.callback_query_handlers,
self.inline_query_handlers, self.inline_query_handlers,
self.chat_member_handlers,
self.my_chat_member_handlers,
]) ])
filters_factory.bind(IsReplyFilter, event_handlers=[ filters_factory.bind(IsReplyFilter, event_handlers=[
self.message_handlers, self.message_handlers,
@ -196,6 +201,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self.channel_post_handlers, self.channel_post_handlers,
self.edited_channel_post_handlers, self.edited_channel_post_handlers,
self.callback_query_handlers, self.callback_query_handlers,
self.my_chat_member_handlers,
self.chat_member_handlers
]) ])
def __del__(self): def __del__(self):
@ -286,6 +293,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
types.PollAnswer.set_current(update.poll_answer) types.PollAnswer.set_current(update.poll_answer)
types.User.set_current(update.poll_answer.user) types.User.set_current(update.poll_answer.user)
return await self.poll_answer_handlers.notify(update.poll_answer) 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: except Exception as e:
err = await self.errors_handlers.notify(update, e) err = await self.errors_handlers.notify(update, e)
if err: if err:
@ -1005,6 +1020,118 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
return decorator 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): def register_errors_handler(self, callback, *custom_filters, exception=None, run_task=None, **kwargs):
""" """
Register handler for errors Register handler for errors
@ -1084,8 +1211,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
if rate is None: if rate is None:
rate = self.throttling_rate_limit rate = self.throttling_rate_limit
if user_id is None and chat_id is None: if user_id is None and chat_id is None:
user_id = types.User.get_current().id chat_obj = types.Chat.get_current()
chat_id = types.Chat.get_current().id chat_id = chat_obj.id if chat_obj else None
user_obj = types.User.get_current()
user_id = user_obj.id if user_obj else None
# Detect current time # Detect current time
now = time.time() now = time.time()
@ -1136,8 +1266,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
raise RuntimeError('This storage does not provide Leaky Bucket') raise RuntimeError('This storage does not provide Leaky Bucket')
if user_id is None and chat_id is None: if user_id is None and chat_id is None:
user_id = types.User.get_current() chat_obj = types.Chat.get_current()
chat_id = types.Chat.get_current() chat_id = chat_obj.id if chat_obj else None
user_obj = types.User.get_current()
user_id = user_obj.id if user_obj else None
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id) bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
data = bucket.get(key, {}) data = bucket.get(key, {})
@ -1158,8 +1291,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
raise RuntimeError('This storage does not provide Leaky Bucket') raise RuntimeError('This storage does not provide Leaky Bucket')
if user_id is None and chat_id is None: if user_id is None and chat_id is None:
user_id = types.User.get_current() chat_obj = types.Chat.get_current()
chat_id = types.Chat.get_current() chat_id = chat_obj.id if chat_obj else None
user_obj = types.User.get_current()
user_id = user_obj.id if user_obj else None
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id) bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
if bucket and key in bucket: if bucket and key in bucket:

View file

@ -10,7 +10,7 @@ from babel.support import LazyProxy
from aiogram import types from aiogram import types
from aiogram.dispatcher.filters.filters import BoundFilter, Filter 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] ChatIDArgumentType = typing.Union[typing.Iterable[typing.Union[int, str]], str, int]
@ -529,6 +529,8 @@ class StateFilter(BoundFilter):
self.states = states self.states = states
def get_target(self, obj): def get_target(self, obj):
if isinstance(obj, CallbackQuery):
return getattr(getattr(getattr(obj, 'message', None),'chat', None), 'id', None), getattr(getattr(obj, 'from_user', None), 'id', None)
return getattr(getattr(obj, 'chat', None), 'id', None), getattr(getattr(obj, 'from_user', None), 'id', None) return getattr(getattr(obj, 'chat', None), 'id', None), getattr(getattr(obj, 'from_user', None), 'id', None)
async def check(self, obj): async def check(self, obj):
@ -604,7 +606,7 @@ class IDFilter(Filter):
return result 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): if isinstance(obj, Message):
user_id = None user_id = None
if obj.from_user is not None: if obj.from_user is not None:
@ -619,6 +621,9 @@ class IDFilter(Filter):
elif isinstance(obj, InlineQuery): elif isinstance(obj, InlineQuery):
user_id = obj.from_user.id user_id = obj.from_user.id
chat_id = None chat_id = None
elif isinstance(obj, ChatMemberUpdated):
user_id = obj.from_user.id
chat_id = obj.chat.id
else: else:
return False return False
@ -663,19 +668,21 @@ class AdminFilter(Filter):
return result 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 user_id = obj.from_user.id
if self._check_current: if self._check_current:
if isinstance(obj, Message): if isinstance(obj, Message):
message = obj chat = obj.chat
elif isinstance(obj, CallbackQuery) and obj.message: elif isinstance(obj, CallbackQuery) and obj.message:
message = obj.message chat = obj.message.chat
elif isinstance(obj, ChatMemberUpdated):
chat = obj.chat
else: else:
return False 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 return False
chat_ids = [message.chat.id] chat_ids = [chat.id]
else: else:
chat_ids = self._chat_ids chat_ids = self._chat_ids
@ -719,11 +726,13 @@ class ChatTypeFilter(BoundFilter):
self.chat_type: typing.Set[str] = set(chat_type) 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): if isinstance(obj, Message):
obj = obj.chat obj = obj.chat
elif isinstance(obj, CallbackQuery): elif isinstance(obj, CallbackQuery):
obj = obj.message.chat obj = obj.message.chat
elif isinstance(obj, ChatMemberUpdated):
obj = obj.chat
else: else:
warnings.warn("ChatTypeFilter doesn't support %s as input", type(obj)) warnings.warn("ChatTypeFilter doesn't support %s as input", type(obj))
return False return False

View file

@ -7,8 +7,10 @@ from .bot_command import BotCommand
from .callback_game import CallbackGame from .callback_game import CallbackGame
from .callback_query import CallbackQuery from .callback_query import CallbackQuery
from .chat import Chat, ChatActions, ChatType from .chat import Chat, ChatActions, ChatType
from .chat_invite_link import ChatInviteLink
from .chat_location import ChatLocation from .chat_location import ChatLocation
from .chat_member import ChatMember, ChatMemberStatus from .chat_member import ChatMember, ChatMemberStatus
from .chat_member_updated import ChatMemberUpdated
from .chat_permissions import ChatPermissions from .chat_permissions import ChatPermissions
from .chat_photo import ChatPhoto from .chat_photo import ChatPhoto
from .chosen_inline_result import ChosenInlineResult from .chosen_inline_result import ChosenInlineResult
@ -40,6 +42,7 @@ from .location import Location
from .login_url import LoginUrl from .login_url import LoginUrl
from .mask_position import MaskPosition from .mask_position import MaskPosition
from .message import ContentType, ContentTypes, Message, ParseMode from .message import ContentType, ContentTypes, Message, ParseMode
from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged
from .message_entity import MessageEntity, MessageEntityType from .message_entity import MessageEntity, MessageEntityType
from .message_id import MessageId from .message_id import MessageId
from .order_info import OrderInfo from .order_info import OrderInfo
@ -67,6 +70,9 @@ from .venue import Venue
from .video import Video from .video import Video
from .video_note import VideoNote from .video_note import VideoNote
from .voice import Voice 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 from .webhook_info import WebhookInfo
__all__ = ( __all__ = (
@ -79,9 +85,11 @@ __all__ = (
'CallbackQuery', 'CallbackQuery',
'Chat', 'Chat',
'ChatActions', 'ChatActions',
'ChatInviteLink',
'ChatLocation', 'ChatLocation',
'ChatMember', 'ChatMember',
'ChatMemberStatus', 'ChatMemberStatus',
'ChatMemberUpdated',
'ChatPermissions', 'ChatPermissions',
'ChatPhoto', 'ChatPhoto',
'ChatType', 'ChatType',
@ -143,6 +151,7 @@ __all__ = (
'MaskPosition', 'MaskPosition',
'MediaGroup', 'MediaGroup',
'Message', 'Message',
'MessageAutoDeleteTimerChanged',
'MessageEntity', 'MessageEntity',
'MessageEntityType', 'MessageEntityType',
'MessageId', 'MessageId',
@ -180,6 +189,9 @@ __all__ = (
'Video', 'Video',
'VideoNote', 'VideoNote',
'Voice', 'Voice',
'VoiceChatEnded',
'VoiceChatParticipantsInvited',
'VoiceChatStarted',
'WebhookInfo', 'WebhookInfo',
'base', 'base',
'fields', 'fields',

View file

@ -212,7 +212,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
""" """
if item in self.props: if item in self.props:
return self.props[item].get_value(self) return self.props[item].get_value(self)
raise KeyError(item) return self.values[item]
def __setitem__(self, key: str, value: typing.Any) -> None: def __setitem__(self, key: str, value: typing.Any) -> None:
""" """
@ -224,6 +224,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
""" """
if key in self.props: if key in self.props:
return self.props[key].set_value(self, value, self.conf.get('parent', None)) return self.props[key].set_value(self, value, self.conf.get('parent', None))
self.values[key] = value
raise KeyError(key) raise KeyError(key)
def __contains__(self, item: str) -> bool: def __contains__(self, item: str) -> bool:

View file

@ -5,6 +5,7 @@ import datetime
import typing import typing
from . import base, fields from . import base, fields
from .chat_invite_link import ChatInviteLink
from .chat_location import ChatLocation from .chat_location import ChatLocation
from .chat_member import ChatMember from .chat_member import ChatMember
from .chat_permissions import ChatPermissions from .chat_permissions import ChatPermissions
@ -113,7 +114,7 @@ class Chat(base.TelegramObject):
async def update_chat(self): async def update_chat(self):
""" """
User this method to update Chat data Use this method to update Chat data
:return: None :return: None
""" """
@ -185,30 +186,47 @@ class Chat(base.TelegramObject):
""" """
return await self.bot.set_chat_description(self.id, description) return await self.bot.set_chat_description(self.id, description)
async def kick(self, user_id: base.Integer, async def kick(self,
until_date: typing.Union[ user_id: base.Integer,
base.Integer, datetime.datetime, datetime.timedelta, None] = None) -> base.Boolean: 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. 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 In the case of supergroups and channels, the user will not be able to return
on their own using invite links, etc., unless unbanned first. 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. 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.
Source: https://core.telegram.org/bots/api#kickchatmember Source: https://core.telegram.org/bots/api#kickchatmember
:param user_id: Unique identifier of the target user :param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer` :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
:return: Returns True on success. 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` :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, async def unban(self,
user_id: base.Integer, user_id: base.Integer,
@ -554,6 +572,41 @@ class Chat(base.TelegramObject):
return self.invite_link return self.invite_link
async def create_invite_link(self,
expire_date: typing.Union[base.Integer, datetime.datetime,
datetime.timedelta, None] = None,
member_limit: typing.Optional[base.Integer] = None,
) -> 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] = None,
member_limit: typing.Optional[base.Integer] = None,
) -> 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): def __int__(self):
return self.id return self.id

View file

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

View file

@ -16,22 +16,24 @@ class ChatMember(base.TelegramObject):
status: base.String = fields.Field() status: base.String = fields.Field()
custom_title: base.String = fields.Field() custom_title: base.String = fields.Field()
is_anonymous: base.Boolean = fields.Field() is_anonymous: base.Boolean = fields.Field()
until_date: datetime.datetime = fields.DateTimeField()
can_be_edited: base.Boolean = fields.Field() 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_post_messages: base.Boolean = fields.Field()
can_edit_messages: base.Boolean = fields.Field() can_edit_messages: base.Boolean = fields.Field()
can_delete_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_restrict_members: base.Boolean = fields.Field()
can_pin_messages: base.Boolean = fields.Field()
can_promote_members: 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() is_member: base.Boolean = fields.Field()
can_send_messages: base.Boolean = fields.Field() can_send_messages: base.Boolean = fields.Field()
can_send_media_messages: base.Boolean = fields.Field() can_send_media_messages: base.Boolean = fields.Field()
can_send_polls: base.Boolean = fields.Field() can_send_polls: base.Boolean = fields.Field()
can_send_other_messages: base.Boolean = fields.Field() can_send_other_messages: base.Boolean = fields.Field()
can_add_web_page_previews: 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: def is_chat_creator(self) -> bool:
return ChatMemberStatus.is_chat_creator(self.status) return ChatMemberStatus.is_chat_creator(self.status)

View file

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

View file

@ -3,9 +3,7 @@ from . import base, fields
class Dice(base.TelegramObject): class Dice(base.TelegramObject):
""" """
This object represents a dice with random value from 1 to 6. This object represents an animated emoji that displays a random value.
(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!)
https://core.telegram.org/bots/api#dice https://core.telegram.org/bots/api#dice
""" """
@ -19,3 +17,4 @@ class DiceEmoji:
BASKETBALL = '🏀' BASKETBALL = '🏀'
FOOTBALL = '' FOOTBALL = ''
SLOT_MACHINE = '🎰' SLOT_MACHINE = '🎰'
BOWLING = '🎳'

View file

@ -311,76 +311,96 @@ class MediaGroup(base.TelegramObject):
self.attach(animation) self.attach(animation)
''' '''
def attach_audio(self, audio: base.InputFile, def attach_audio(self, audio: typing.Union[InputMediaAudio, base.InputFile],
thumb: typing.Union[base.InputFile, base.String] = None, thumb: typing.Union[base.InputFile, base.String] = None,
caption: base.String = None, caption: base.String = None,
width: base.Integer = None, height: base.Integer = None,
duration: base.Integer = None, duration: base.Integer = None,
performer: base.String = None, performer: base.String = None,
title: base.String = None, title: base.String = None,
parse_mode: base.String = None): parse_mode: base.String = None,
caption_entities: typing.Optional[typing.List[MessageEntity]] = None):
""" """
Attach animation Attach audio
:param audio: :param audio:
:param thumb: :param thumb:
:param caption: :param caption:
:param width:
:param height:
:param duration: :param duration:
:param performer: :param performer:
:param title: :param title:
:param parse_mode: :param parse_mode:
:param caption_entities:
""" """
if not isinstance(audio, InputMedia): if not isinstance(audio, InputMedia):
audio = InputMediaAudio(media=audio, thumb=thumb, caption=caption, audio = InputMediaAudio(media=audio, thumb=thumb, caption=caption,
width=width, height=height, duration=duration, duration=duration,
performer=performer, title=title, performer=performer, title=title,
parse_mode=parse_mode) parse_mode=parse_mode,
caption_entities=caption_entities)
self.attach(audio) self.attach(audio)
def attach_document(self, document: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None, def attach_document(self, document: typing.Union[InputMediaDocument, base.InputFile],
caption: base.String = None, parse_mode: base.String = None): thumb: typing.Union[base.InputFile, base.String] = None,
caption: base.String = None, parse_mode: base.String = None,
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
disable_content_type_detection: typing.Optional[base.Boolean] = None):
""" """
Attach document Attach document
:param parse_mode: :param document:
:param caption: :param caption:
:param thumb: :param thumb:
:param document: :param parse_mode:
:param caption_entities:
:param disable_content_type_detection:
""" """
if not isinstance(document, InputMedia): if not isinstance(document, InputMedia):
document = InputMediaDocument(media=document, thumb=thumb, caption=caption, parse_mode=parse_mode) document = InputMediaDocument(media=document, thumb=thumb, caption=caption,
parse_mode=parse_mode, caption_entities=caption_entities,
disable_content_type_detection=disable_content_type_detection)
self.attach(document) self.attach(document)
def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile], def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile],
caption: base.String = None): caption: base.String = None, parse_mode: base.String = None,
caption_entities: typing.Optional[typing.List[MessageEntity]] = None):
""" """
Attach photo Attach photo
:param photo: :param photo:
:param caption: :param caption:
:param parse_mode:
:param caption_entities:
""" """
if not isinstance(photo, InputMedia): if not isinstance(photo, InputMedia):
photo = InputMediaPhoto(media=photo, caption=caption) photo = InputMediaPhoto(media=photo, caption=caption, parse_mode=parse_mode,
caption_entities=caption_entities)
self.attach(photo) self.attach(photo)
def attach_video(self, video: typing.Union[InputMediaVideo, base.InputFile], def attach_video(self, video: typing.Union[InputMediaVideo, base.InputFile],
thumb: typing.Union[base.InputFile, base.String] = None, thumb: typing.Union[base.InputFile, base.String] = None,
caption: base.String = None, caption: base.String = None,
width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None): width: base.Integer = None, height: base.Integer = None,
duration: base.Integer = None, parse_mode: base.String = None,
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
supports_streaming: base.Boolean = None):
""" """
Attach video Attach video
:param video: :param video:
:param thumb:
:param caption: :param caption:
:param width: :param width:
:param height: :param height:
:param duration: :param duration:
:param parse_mode:
:param caption_entities:
:param supports_streaming:
""" """
if not isinstance(video, InputMedia): if not isinstance(video, InputMedia):
video = InputMediaVideo(media=video, thumb=thumb, caption=caption, video = InputMediaVideo(media=video, thumb=thumb, caption=caption,
width=width, height=height, duration=duration) width=width, height=height, duration=duration,
parse_mode=parse_mode, caption_entities=caption_entities,
supports_streaming=supports_streaming)
self.attach(video) self.attach(video)
def to_python(self) -> typing.List: def to_python(self) -> typing.List:

View file

@ -4,10 +4,6 @@ import datetime
import functools import functools
import typing 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 . import base, fields
from .animation import Animation from .animation import Animation
from .audio import Audio from .audio import Audio
@ -21,6 +17,7 @@ from .inline_keyboard import InlineKeyboardMarkup
from .input_media import InputMedia, MediaGroup from .input_media import InputMedia, MediaGroup
from .invoice import Invoice from .invoice import Invoice
from .location import Location from .location import Location
from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged
from .message_entity import MessageEntity from .message_entity import MessageEntity
from .message_id import MessageId from .message_id import MessageId
from .passport_data import PassportData from .passport_data import PassportData
@ -35,6 +32,12 @@ from .venue import Venue
from .video import Video from .video import Video
from .video_note import VideoNote from .video_note import VideoNote
from .voice import Voice 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.text_decorations import html_decoration, markdown_decoration
class Message(base.TelegramObject): class Message(base.TelegramObject):
@ -86,6 +89,7 @@ class Message(base.TelegramObject):
group_chat_created: base.Boolean = fields.Field() group_chat_created: base.Boolean = fields.Field()
supergroup_chat_created: base.Boolean = fields.Field() supergroup_chat_created: base.Boolean = fields.Field()
channel_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_to_chat_id: base.Integer = fields.Field()
migrate_from_chat_id: base.Integer = fields.Field() migrate_from_chat_id: base.Integer = fields.Field()
pinned_message: Message = fields.Field(base="Message") pinned_message: Message = fields.Field(base="Message")
@ -94,6 +98,9 @@ class Message(base.TelegramObject):
connected_website: base.String = fields.Field() connected_website: base.String = fields.Field()
passport_data: PassportData = fields.Field(base=PassportData) passport_data: PassportData = fields.Field(base=PassportData)
proximity_alert_triggered: ProximityAlertTriggered = fields.Field(base=ProximityAlertTriggered) 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) reply_markup: InlineKeyboardMarkup = fields.Field(base=InlineKeyboardMarkup)
@property @property
@ -139,6 +146,8 @@ class Message(base.TelegramObject):
return ContentType.SUCCESSFUL_PAYMENT return ContentType.SUCCESSFUL_PAYMENT
if self.connected_website: if self.connected_website:
return ContentType.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: if self.migrate_from_chat_id:
return ContentType.MIGRATE_FROM_CHAT_ID return ContentType.MIGRATE_FROM_CHAT_ID
if self.migrate_to_chat_id: if self.migrate_to_chat_id:
@ -157,6 +166,12 @@ class Message(base.TelegramObject):
return ContentType.PASSPORT_DATA return ContentType.PASSPORT_DATA
if self.proximity_alert_triggered: if self.proximity_alert_triggered:
return ContentType.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 return ContentType.UNKNOWN
@ -960,6 +975,9 @@ class Message(base.TelegramObject):
live_period: typing.Optional[base.Integer] = None, live_period: typing.Optional[base.Integer] = None,
disable_notification: typing.Optional[base.Boolean] = None, disable_notification: typing.Optional[base.Boolean] = None,
allow_sending_without_reply: typing.Optional[base.Boolean] = None, allow_sending_without_reply: typing.Optional[base.Boolean] = None,
horizontal_accuracy: typing.Optional[base.Float] = None,
heading: typing.Optional[base.Integer] = None,
proximity_alert_radius: typing.Optional[base.Integer] = None,
reply_markup: typing.Union[ reply_markup: typing.Union[
InlineKeyboardMarkup, InlineKeyboardMarkup,
ReplyKeyboardMarkup, ReplyKeyboardMarkup,
@ -980,9 +998,22 @@ class Message(base.TelegramObject):
:param longitude: Longitude of the location :param longitude: Longitude of the location
:type longitude: :obj:`base.Float` :type longitude: :obj:`base.Float`
:param horizontal_accuracy: The radius of uncertainty for the location,
measured in meters; 0-1500
:type horizontal_accuracy: :obj:`typing.Optional[base.Float]`
:param live_period: Period in seconds for which the location will be updated :param live_period: Period in seconds for which the location will be updated
:type live_period: :obj:`typing.Optional[base.Integer]` :type live_period: :obj:`typing.Optional[base.Integer]`
:param heading: For live locations, a direction in which the user is moving,
in degrees. Must be between 1 and 360 if specified.
:type heading: :obj:`typing.Optional[base.Integer]`
:param proximity_alert_radius: For live locations, a maximum distance for
proximity alerts about approaching another chat member, in meters. Must
be between 1 and 100000 if specified.
:type proximity_alert_radius: :obj:`typing.Optional[base.Integer]`
:param disable_notification: Sends the message silently. Users will receive a notification with no sound. :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
:type disable_notification: :obj:`typing.Optional[base.Boolean]` :type disable_notification: :obj:`typing.Optional[base.Boolean]`
@ -1005,7 +1036,10 @@ class Message(base.TelegramObject):
chat_id=self.chat.id, chat_id=self.chat.id,
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
horizontal_accuracy=horizontal_accuracy,
live_period=live_period, live_period=live_period,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
disable_notification=disable_notification, disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None, reply_to_message_id=self.message_id if reply else None,
allow_sending_without_reply=allow_sending_without_reply, allow_sending_without_reply=allow_sending_without_reply,
@ -1381,6 +1415,30 @@ class Message(base.TelegramObject):
allow_sending_without_reply=allow_sending_without_reply, allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup, reply_markup=reply_markup,
) )
async def answer_chat_action(
self,
action: base.String,
) -> base.Boolean:
"""
Use this method when you need to tell the user that something is happening on the bot's side.
The status is set for 5 seconds or less
(when a message arrives from your bot, Telegram clients clear its typing status).
We only recommend using this method when a response from the bot will take
a noticeable amount of time to arrive.
Source: https://core.telegram.org/bots/api#sendchataction
:param action: Type of action to broadcast
:type action: :obj:`base.String`
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
return await self.bot.send_chat_action(
chat_id=self.chat.id,
action=action,
)
async def reply( async def reply(
self, self,
@ -2057,6 +2115,9 @@ class Message(base.TelegramObject):
longitude: base.Float, longitude: base.Float,
live_period: typing.Optional[base.Integer] = None, live_period: typing.Optional[base.Integer] = None,
disable_notification: typing.Optional[base.Boolean] = None, disable_notification: typing.Optional[base.Boolean] = None,
horizontal_accuracy: typing.Optional[base.Float] = None,
heading: typing.Optional[base.Integer] = None,
proximity_alert_radius: typing.Optional[base.Integer] = None,
reply_markup: typing.Union[ reply_markup: typing.Union[
InlineKeyboardMarkup, InlineKeyboardMarkup,
ReplyKeyboardMarkup, ReplyKeyboardMarkup,
@ -2073,18 +2134,37 @@ class Message(base.TelegramObject):
:param latitude: Latitude of the location :param latitude: Latitude of the location
:type latitude: :obj:`base.Float` :type latitude: :obj:`base.Float`
:param longitude: Longitude of the location :param longitude: Longitude of the location
:type longitude: :obj:`base.Float` :type longitude: :obj:`base.Float`
:param horizontal_accuracy: The radius of uncertainty for the location,
measured in meters; 0-1500
:type horizontal_accuracy: :obj:`typing.Optional[base.Float]`
:param live_period: Period in seconds for which the location will be updated :param live_period: Period in seconds for which the location will be updated
:type live_period: :obj:`typing.Optional[base.Integer]` :type live_period: :obj:`typing.Optional[base.Integer]`
:param heading: For live locations, a direction in which the user is moving,
in degrees. Must be between 1 and 360 if specified.
:type heading: :obj:`typing.Optional[base.Integer]`
:param proximity_alert_radius: For live locations, a maximum distance for
proximity alerts about approaching another chat member, in meters. Must
be between 1 and 100000 if specified.
:type proximity_alert_radius: :obj:`typing.Optional[base.Integer]`
:param disable_notification: Sends the message silently. Users will receive a notification with no sound. :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
:type disable_notification: :obj:`typing.Optional[base.Boolean]` :type disable_notification: :obj:`typing.Optional[base.Boolean]`
:param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard, :param reply_markup: Additional interface options. A JSON-serialized object for an inline keyboard,
custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]` types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
:param reply: fill 'reply_to_message_id' :param reply: fill 'reply_to_message_id'
:type reply: :obj:`base.Boolean` :type reply: :obj:`base.Boolean`
:return: On success, the sent Message is returned. :return: On success, the sent Message is returned.
:rtype: :obj:`types.Message` :rtype: :obj:`types.Message`
""" """
@ -2092,7 +2172,10 @@ class Message(base.TelegramObject):
chat_id=self.chat.id, chat_id=self.chat.id,
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
horizontal_accuracy=horizontal_accuracy,
live_period=live_period, live_period=live_period,
heading=heading,
proximity_alert_radius=proximity_alert_radius,
disable_notification=disable_notification, disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None, reply_to_message_id=self.message_id if reply else None,
reply_markup=reply_markup, reply_markup=reply_markup,
@ -2740,11 +2823,6 @@ class Message(base.TelegramObject):
message_id=self.message_id, message_id=self.message_id,
) )
@deprecated(
"This method deprecated since Bot API 4.5. Use method `copy_to` instead. \n"
"Read more: https://core.telegram.org/bots/api#copymessage",
stacklevel=3
)
async def send_copy( async def send_copy(
self: Message, self: Message,
chat_id: typing.Union[str, int], chat_id: typing.Union[str, int],
@ -2847,6 +2925,14 @@ class Message(base.TelegramObject):
return await self.bot.send_poll( return await self.bot.send_poll(
question=self.poll.question, question=self.poll.question,
options=[option.text for option in self.poll.options], options=[option.text for option in self.poll.options],
is_anonymous=self.poll.is_anonymous,
allows_multiple_answers=self.poll.allows_multiple_answers
**kwargs,
)
elif self.dice:
kwargs.pop("parse_mode")
return await self.bot.send_dice(
emoji=self.dice.emoji,
**kwargs, **kwargs,
) )
else: else:
@ -2936,6 +3022,7 @@ class ContentType(helper.Helper):
INVOICE = helper.Item() # invoice INVOICE = helper.Item() # invoice
SUCCESSFUL_PAYMENT = helper.Item() # successful_payment SUCCESSFUL_PAYMENT = helper.Item() # successful_payment
CONNECTED_WEBSITE = helper.Item() # connected_website 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_TO_CHAT_ID = helper.Item() # migrate_to_chat_id
MIGRATE_FROM_CHAT_ID = helper.Item() # migrate_from_chat_id MIGRATE_FROM_CHAT_ID = helper.Item() # migrate_from_chat_id
PINNED_MESSAGE = helper.Item() # pinned_message PINNED_MESSAGE = helper.Item() # pinned_message
@ -2945,6 +3032,9 @@ class ContentType(helper.Helper):
GROUP_CHAT_CREATED = helper.Item() # group_chat_created GROUP_CHAT_CREATED = helper.Item() # group_chat_created
PASSPORT_DATA = helper.Item() # passport_data PASSPORT_DATA = helper.Item() # passport_data
PROXIMITY_ALERT_TRIGGERED = helper.Item() # proximity_alert_triggered 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 UNKNOWN = helper.Item() # unknown
ANY = helper.Item() # any ANY = helper.Item() # any

View file

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

View file

@ -3,6 +3,7 @@ from __future__ import annotations
from . import base from . import base
from . import fields from . import fields
from .callback_query import CallbackQuery from .callback_query import CallbackQuery
from .chat_member_updated import ChatMemberUpdated
from .chosen_inline_result import ChosenInlineResult from .chosen_inline_result import ChosenInlineResult
from .inline_query import InlineQuery from .inline_query import InlineQuery
from .message import Message from .message import Message
@ -31,6 +32,8 @@ class Update(base.TelegramObject):
pre_checkout_query: PreCheckoutQuery = fields.Field(base=PreCheckoutQuery) pre_checkout_query: PreCheckoutQuery = fields.Field(base=PreCheckoutQuery)
poll: Poll = fields.Field(base=Poll) poll: Poll = fields.Field(base=Poll)
poll_answer: PollAnswer = fields.Field(base=PollAnswer) 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): def __hash__(self):
return self.update_id return self.update_id
@ -61,6 +64,8 @@ class AllowedUpdates(helper.Helper):
PRE_CHECKOUT_QUERY = helper.ListItem() # pre_checkout_query PRE_CHECKOUT_QUERY = helper.ListItem() # pre_checkout_query
POLL = helper.ListItem() # poll POLL = helper.ListItem() # poll
POLL_ANSWER = helper.ListItem() # poll_answer 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 = deprecated.DeprecatedReadOnlyClassVar(
"`CHOSEN_INLINE_QUERY` is a deprecated value for allowed update. " "`CHOSEN_INLINE_QUERY` is a deprecated value for allowed update. "

View file

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

View file

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

View file

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

View file

@ -494,6 +494,20 @@ class MethodIsNotAvailable(BadRequest):
match = "Method is available only for supergroups" match = "Method is available only for supergroups"
class CantRestrictChatOwner(BadRequest):
"""
Raises when bot restricts the chat owner
"""
match = 'Can\'t remove chat owner'
class UserIsAnAdministratorOfTheChat(BadRequest):
"""
Raises when bot restricts the chat admin
"""
match = 'User is an administrator of the chat'
class NotFound(TelegramAPIError, _MatchErrorMixin): class NotFound(TelegramAPIError, _MatchErrorMixin):
__group = True __group = True

View file

@ -15,12 +15,13 @@ def split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]:
return [text[i:i + length] for i in range(0, len(text), length)] return [text[i:i + length] for i in range(0, len(text), length)]
def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]: def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH, split_separator: str = ' ') -> typing.List[str]:
""" """
Split long text Split long text
:param text: :param text:
:param length: :param length:
:param split_separator
:return: :return:
""" """
# TODO: More informative description # TODO: More informative description
@ -30,7 +31,7 @@ def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[
while temp_text: while temp_text:
if len(temp_text) > length: if len(temp_text) > length:
try: try:
split_pos = temp_text[:length].rindex(' ') split_pos = temp_text[:length].rindex(split_separator)
except ValueError: except ValueError:
split_pos = length split_pos = length
if split_pos < length // 4 * 3: if split_pos < length // 4 * 3:

View file

@ -22,7 +22,7 @@ Welcome to aiogram's documentation!
:target: https://pypi.python.org/pypi/aiogram :target: https://pypi.python.org/pypi/aiogram
:alt: Supported python versions :alt: Supported python versions
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.0-blue.svg?style=flat-square&logo=telegram .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.1-blue.svg?style=flat-square&logo=telegram
:target: https://core.telegram.org/bots/api :target: https://core.telegram.org/bots/api
:alt: Telegram Bot API :alt: Telegram Bot API

View file

@ -32,7 +32,7 @@ From sources
$ cd aiogram $ cd aiogram
$ python setup.py install $ python setup.py install
Or if you want to install stable version (The same with version form PyPi): Or if you want to install stable version (The same with version from PyPi):
.. code-block:: bash .. code-block:: bash

View file

@ -22,7 +22,7 @@ dp = Dispatcher(bot)
@dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.CHANNEL]) @dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.CHANNEL])
async def send_welcome(message: types.Message): async def send_welcome(message: types.Message):
""" """
This handler will be called when user sends `/start` or `/help` command This handler will be called when user sends message in private chat or channel
""" """
await message.reply("Hi!\nI'm hearing your messages in private chats and channels") await message.reply("Hi!\nI'm hearing your messages in private chats and channels")
@ -33,7 +33,7 @@ async def send_welcome(message: types.Message):
@dp.message_handler(chat_type=ChatType.PRIVATE) @dp.message_handler(chat_type=ChatType.PRIVATE)
async def send_welcome(message: types.Message): async def send_welcome(message: types.Message):
""" """
This handler will be called when user sends `/start` or `/help` command This handler will be called when user sends message in private chat
""" """
await message.reply("Hi!\nI'm hearing your messages only in private chats") await message.reply("Hi!\nI'm hearing your messages only in private chats")

View file

@ -0,0 +1,34 @@
# NOTE: This is an example of an integration between
# externally created Application object and the aiogram's dispatcher
# This can be used for a custom route, for instance
from aiogram import Bot, Dispatcher, types
from aiogram.dispatcher.webhook import configure_app
from aiohttp import web
bot = Bot(token=config.bot_token)
dp = Dispatcher(bot)
@dp.message_handler(commands=["start"])
async def cmd_start(message: types.Message):
await message.reply("start!")
# handle /api route
async def api_handler(request):
return web.json_response({"status": "OK"}, status=200)
app = web.Application()
# add a custom route
app.add_routes([web.post('/api', api_handler)])
# every request to /bot route will be retransmitted to dispatcher to be handled
# as a bot update
configure_app(dp, app, "/bot")
if __name__ == '__main__':
web.run_app(app, port=9000)