mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Merge branch 'dev-2.x'
This commit is contained in:
commit
3487658e33
13 changed files with 237 additions and 10 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
[](https://pypi.python.org/pypi/aiogram)
|
[](https://pypi.python.org/pypi/aiogram)
|
||||||
[](https://pypi.python.org/pypi/aiogram)
|
[](https://pypi.python.org/pypi/aiogram)
|
||||||
[](https://pypi.python.org/pypi/aiogram)
|
[](https://pypi.python.org/pypi/aiogram)
|
||||||
[](https://core.telegram.org/bots/api)
|
[](https://core.telegram.org/bots/api)
|
||||||
[](http://docs.aiogram.dev/en/latest/?badge=latest)
|
[](http://docs.aiogram.dev/en/latest/?badge=latest)
|
||||||
[](https://github.com/aiogram/aiogram/issues)
|
[](https://github.com/aiogram/aiogram/issues)
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
|
||||||
|
|
@ -43,5 +43,5 @@ __all__ = (
|
||||||
'utils',
|
'utils',
|
||||||
)
|
)
|
||||||
|
|
||||||
__version__ = '2.16'
|
__version__ = '2.17'
|
||||||
__api_version__ = '5.4'
|
__api_version__ = '5.5'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
125
examples/custom_filter_example.py
Normal file
125
examples/custom_filter_example.py
Normal 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)
|
||||||
14
tests/test_dispatcher/test_fsm_context.py
Normal file
14
tests/test_dispatcher/test_fsm_context.py
Normal 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"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue