Merge branch 'dev-2.x'

This commit is contained in:
Alex Root Junior 2021-12-07 20:07:53 +02:00
commit 3487658e33
13 changed files with 237 additions and 10 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.4-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.5-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

@ -43,5 +43,5 @@ __all__ = (
'utils', 'utils',
) )
__version__ = '2.16' __version__ = '2.17'
__api_version__ = '5.4' __api_version__ = '5.5'

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.4 List is updated to Bot API 5.5
""" """
mode = HelperMode.lowerCamelCase mode = HelperMode.lowerCamelCase
@ -230,6 +230,8 @@ class Methods(Helper):
RESTRICT_CHAT_MEMBER = Item() # restrictChatMember RESTRICT_CHAT_MEMBER = Item() # restrictChatMember
PROMOTE_CHAT_MEMBER = Item() # promoteChatMember PROMOTE_CHAT_MEMBER = Item() # promoteChatMember
SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE = Item() # setChatAdministratorCustomTitle SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE = Item() # setChatAdministratorCustomTitle
BAN_CHAT_SENDER_CHAT = Item() # banChatSenderChat
UNBAN_CHAT_SENDER_CHAT = Item() # unbanChatSenderChat
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 CREATE_CHAT_INVITE_LINK = Item() # createChatInviteLink

View file

@ -1814,6 +1814,62 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return await self.request(api.Methods.SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE, payload) return await self.request(api.Methods.SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE, payload)
async def ban_chat_sender_chat(
self,
chat_id: typing.Union[base.Integer, base.String],
sender_chat_id: base.Integer,
until_date: typing.Union[
base.Integer, datetime.datetime, datetime.timedelta, None
] = None,
):
"""Ban a channel chat in a supergroup or a channel.
The owner of the chat will not be able to send messages and join
live streams on behalf of the chat, unless it is unbanned first.
The bot must be an administrator in the supergroup or channel
for this to work and must have the appropriate administrator
rights. Returns True on success.
Source: https://core.telegram.org/bots/api#banchatsenderchat
:param chat_id: Unique identifier for the target chat or
username of the target channel (in the format
@channelusername)
:param sender_chat_id: Unique identifier of the target sender
chat
:param until_date: Date when the sender chat will be unbanned,
unix time. If the chat is banned for more than 366 days or
less than 30 seconds from the current time they are
considered to be banned forever.
"""
until_date = prepare_arg(until_date)
payload = generate_payload(**locals())
return await self.request(api.Methods.BAN_CHAT_SENDER_CHAT, payload)
async def unban_chat_sender_chat(
self,
chat_id: typing.Union[base.Integer, base.String],
sender_chat_id: base.Integer,
):
"""Unban a previously banned channel chat in a supergroup or
channel.
The bot must be an administrator for this to work and must have
the appropriate administrator rights. Returns True on success.
Source: https://core.telegram.org/bots/api#unbanchatsenderchat
:param chat_id: Unique identifier for the target chat or
username of the target channel (in the format
@channelusername)
:param sender_chat_id: Unique identifier of the target sender
chat
"""
payload = generate_payload(**locals())
return await self.request(api.Methods.UNBAN_CHAT_SENDER_CHAT, payload)
async def set_chat_permissions(self, chat_id: typing.Union[base.Integer, base.String], async def set_chat_permissions(self, chat_id: typing.Union[base.Integer, base.String],
permissions: types.ChatPermissions) -> base.Boolean: permissions: types.ChatPermissions) -> base.Boolean:
""" """

View file

@ -408,6 +408,8 @@ class FSMContextProxy:
def update(self, data=None, **kwargs): def update(self, data=None, **kwargs):
self._check_closed() self._check_closed()
if data is None:
data = {}
self._data.update(data, **kwargs) self._data.update(data, **kwargs)
def pop(self, key, default=None): def pop(self, key, default=None):

View file

@ -74,7 +74,7 @@ allow_ip(TELEGRAM_SUBNET_1, TELEGRAM_SUBNET_2)
class WebhookRequestHandler(web.View): class WebhookRequestHandler(web.View):
""" """
Simple Wehhook request handler for aiohttp web server. Simple Webhook request handler for aiohttp web server.
You need to register that in app: You need to register that in app:
@ -145,7 +145,7 @@ class WebhookRequestHandler(web.View):
web_response = web.Response(text='ok') web_response = web.Response(text='ok')
if self.request.app.get('RETRY_AFTER', None): if self.request.app.get('RETRY_AFTER', None):
web_response.headers['Retry-After'] = self.request.app['RETRY_AFTER'] web_response.headers['Retry-After'] = str(self.request.app['RETRY_AFTER'])
return web_response return web_response

View file

@ -30,12 +30,14 @@ class Chat(base.TelegramObject):
all_members_are_administrators: base.Boolean = fields.Field() all_members_are_administrators: base.Boolean = fields.Field()
photo: ChatPhoto = fields.Field(base=ChatPhoto) photo: ChatPhoto = fields.Field(base=ChatPhoto)
bio: base.String = fields.Field() bio: base.String = fields.Field()
has_private_forwards: base.Boolean = fields.Field()
description: base.String = fields.Field() description: base.String = fields.Field()
invite_link: base.String = fields.Field() invite_link: base.String = fields.Field()
pinned_message: 'Message' = fields.Field(base='Message') pinned_message: 'Message' = fields.Field(base='Message')
permissions: ChatPermissions = fields.Field(base=ChatPermissions) permissions: ChatPermissions = fields.Field(base=ChatPermissions)
slow_mode_delay: base.Integer = fields.Field() slow_mode_delay: base.Integer = fields.Field()
message_auto_delete_time: base.Integer = fields.Field() message_auto_delete_time: base.Integer = fields.Field()
has_protected_content: base.Boolean = fields.Field()
sticker_set_name: base.String = fields.Field() sticker_set_name: base.String = fields.Field()
can_set_sticker_set: base.Boolean = fields.Field() can_set_sticker_set: base.Boolean = fields.Field()
linked_chat_id: base.Integer = fields.Field() linked_chat_id: base.Integer = fields.Field()
@ -621,6 +623,30 @@ class Chat(base.TelegramObject):
message_id=message_id, message_id=message_id,
) )
async def ban_sender_chat(
self,
sender_chat_id: base.Integer,
until_date: typing.Union[
base.Integer, datetime.datetime, datetime.timedelta, None
] = None,
):
"""Shortcut for banChatSenderChat method."""
return await self.bot.ban_chat_sender_chat(
chat_id=self.id,
sender_chat_id=sender_chat_id,
until_date=until_date,
)
async def unban_sender_chat(
self,
sender_chat_id: base.Integer,
):
"""Shortcut for unbanChatSenderChat method."""
return await self.bot.unban_chat_sender_chat(
chat_id=self.id,
sender_chat_id=sender_chat_id,
)
def __int__(self): def __int__(self):
return self.id return self.id

View file

@ -118,7 +118,7 @@ class Field(BaseField):
class ListField(Field): class ListField(Field):
""" """
Field contains list ob objects The field contains a list of objects
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -162,7 +162,7 @@ class ListOfLists(Field):
class DateTimeField(Field): class DateTimeField(Field):
""" """
In this field st_ored datetime In this field stored datetime
in: unixtime in: unixtime
out: datetime out: datetime

View file

@ -58,9 +58,11 @@ class Message(base.TelegramObject):
forward_from_message_id: base.Integer = fields.Field() forward_from_message_id: base.Integer = fields.Field()
forward_signature: base.String = fields.Field() forward_signature: base.String = fields.Field()
forward_date: datetime.datetime = fields.DateTimeField() forward_date: datetime.datetime = fields.DateTimeField()
is_automatic_forward: base.Boolean = fields.Field()
reply_to_message: Message = fields.Field(base="Message") reply_to_message: Message = fields.Field(base="Message")
via_bot: User = fields.Field(base=User) via_bot: User = fields.Field(base=User)
edit_date: datetime.datetime = fields.DateTimeField() edit_date: datetime.datetime = fields.DateTimeField()
has_protected_content: base.Boolean = fields.Field()
media_group_id: base.String = fields.Field() media_group_id: base.String = fields.Field()
author_signature: base.String = fields.Field() author_signature: base.String = fields.Field()
forward_sender_name: base.String = fields.Field() forward_sender_name: base.String = fields.Field()

View file

@ -79,7 +79,7 @@ class HelperMode(Helper):
@classmethod @classmethod
def _snake_case(cls, text): def _snake_case(cls, text):
""" """
Transform text to snake cale (Based on SCREAMING_SNAKE_CASE) Transform text to snake case (Based on SCREAMING_SNAKE_CASE)
:param text: :param text:
:return: :return:

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.4-blue.svg?style=flat-square&logo=telegram .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.5-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

@ -0,0 +1,125 @@
from typing import List, Union
from aiogram import Bot, Dispatcher, executor, types
from aiogram.dispatcher.filters import BoundFilter
API_TOKEN = "BOT_TOKEN_HERE"
ADMIN_IDS = [
000000000,
111111111,
222222222,
333333333,
444444444,
]
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
class GlobalAdminFilter(BoundFilter):
"""
Check if the user is a bot admin
"""
key = "global_admin"
def __init__(self, global_admin: bool):
self.global_admin = global_admin
async def check(self, obj: Union[types.Message, types.CallbackQuery]):
user = obj.from_user
if user.id in ADMIN_IDS:
return self.global_admin is True
return self.global_admin is False
class MimeTypeFilter(BoundFilter):
"""
Check document mime_type
"""
key = "mime_type"
def __init__(self, mime_type: Union[str, List[str]]):
if isinstance(mime_type, str):
self.mime_types = [mime_type]
elif isinstance(mime_type, list):
self.mime_types = mime_type
else:
raise ValueError(
f"filter mime_types must be a str or list of str, not {type(mime_type).__name__}"
)
async def check(self, obj: types.Message):
if not obj.document:
return False
if obj.document.mime_type in self.mime_types:
return True
return False
class LettersInMessageFilter(BoundFilter):
"""
Checking for the number of characters in a message/callback_data
"""
key = "letters"
def __init__(self, letters: int):
if isinstance(letters, int):
self.letters = letters
else:
raise ValueError(
f"filter letters must be a int, not {type(letters).__name__}"
)
async def check(self, obj: Union[types.Message, types.CallbackQuery]):
data = obj.text or obj.data
if data:
letters_in_message = len(data)
if letters_in_message > self.letters:
return False
return {"letters": letters_in_message}
return False
# Binding filters
dp.filters_factory.bind(
GlobalAdminFilter,
exclude_event_handlers=[dp.channel_post_handlers, dp.edited_channel_post_handlers],
)
dp.filters_factory.bind(MimeTypeFilter, event_handlers=[dp.message_handlers])
dp.filters_factory.bind(LettersInMessageFilter)
@dp.message_handler(letters=5)
async def handle_letters_in_message(message: types.Message, letters: int):
await message.answer(f"Message too short!\nYou sent only {letters} letters")
@dp.message_handler(content_types=types.ContentTypes.DOCUMENT, mime_type="text/plain")
async def handle_txt_documents(message: types.Message):
await message.answer("This is a text file!")
@dp.message_handler(
content_types=types.ContentTypes.DOCUMENT, mime_type=["image/jpeg", "image/png"]
)
async def handle_photo_documents(message: types.Message):
await message.answer("This is a photo file!")
@dp.message_handler(global_admin=True)
async def handle_admins(message: types.Message):
await message.answer("Congratulations, you are global admin!")
if __name__ == "__main__":
allowed_updates = types.AllowedUpdates.MESSAGE | types.AllowedUpdates.CALLBACK_QUERY
executor.start_polling(dp, allowed_updates=allowed_updates, skip_updates=True)

View file

@ -0,0 +1,14 @@
import pytest
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import FSMContext
class TestFSMContext:
@pytest.mark.asyncio
async def test_update_data(self):
context = FSMContext(MemoryStorage(), chat=1, user=1)
async with context.proxy() as data:
data.update(key1="value1", key2="value2")
async with context.proxy() as data:
assert data['key1'] == "value1"
assert data['key2'] == "value2"