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://core.telegram.org/bots/api)
|
||||
[](https://core.telegram.org/bots/api)
|
||||
[](http://docs.aiogram.dev/en/latest/?badge=latest)
|
||||
[](https://github.com/aiogram/aiogram/issues)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
|
|
|||
|
|
@ -43,5 +43,5 @@ __all__ = (
|
|||
'utils',
|
||||
)
|
||||
|
||||
__version__ = '2.16'
|
||||
__api_version__ = '5.4'
|
||||
__version__ = '2.17'
|
||||
__api_version__ = '5.5'
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ class Methods(Helper):
|
|||
"""
|
||||
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
|
||||
|
||||
|
|
@ -230,6 +230,8 @@ class Methods(Helper):
|
|||
RESTRICT_CHAT_MEMBER = Item() # restrictChatMember
|
||||
PROMOTE_CHAT_MEMBER = Item() # promoteChatMember
|
||||
SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE = Item() # setChatAdministratorCustomTitle
|
||||
BAN_CHAT_SENDER_CHAT = Item() # banChatSenderChat
|
||||
UNBAN_CHAT_SENDER_CHAT = Item() # unbanChatSenderChat
|
||||
SET_CHAT_PERMISSIONS = Item() # setChatPermissions
|
||||
EXPORT_CHAT_INVITE_LINK = Item() # exportChatInviteLink
|
||||
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)
|
||||
|
||||
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],
|
||||
permissions: types.ChatPermissions) -> base.Boolean:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -408,6 +408,8 @@ class FSMContextProxy:
|
|||
def update(self, data=None, **kwargs):
|
||||
self._check_closed()
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
self._data.update(data, **kwargs)
|
||||
|
||||
def pop(self, key, default=None):
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ allow_ip(TELEGRAM_SUBNET_1, TELEGRAM_SUBNET_2)
|
|||
|
||||
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:
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ class WebhookRequestHandler(web.View):
|
|||
web_response = web.Response(text='ok')
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -30,12 +30,14 @@ class Chat(base.TelegramObject):
|
|||
all_members_are_administrators: base.Boolean = fields.Field()
|
||||
photo: ChatPhoto = fields.Field(base=ChatPhoto)
|
||||
bio: base.String = fields.Field()
|
||||
has_private_forwards: base.Boolean = fields.Field()
|
||||
description: base.String = fields.Field()
|
||||
invite_link: base.String = fields.Field()
|
||||
pinned_message: 'Message' = fields.Field(base='Message')
|
||||
permissions: ChatPermissions = fields.Field(base=ChatPermissions)
|
||||
slow_mode_delay: base.Integer = fields.Field()
|
||||
message_auto_delete_time: base.Integer = fields.Field()
|
||||
has_protected_content: base.Boolean = fields.Field()
|
||||
sticker_set_name: base.String = fields.Field()
|
||||
can_set_sticker_set: base.Boolean = fields.Field()
|
||||
linked_chat_id: base.Integer = fields.Field()
|
||||
|
|
@ -621,6 +623,30 @@ class Chat(base.TelegramObject):
|
|||
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):
|
||||
return self.id
|
||||
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class Field(BaseField):
|
|||
|
||||
class ListField(Field):
|
||||
"""
|
||||
Field contains list ob objects
|
||||
The field contains a list of objects
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -162,7 +162,7 @@ class ListOfLists(Field):
|
|||
|
||||
class DateTimeField(Field):
|
||||
"""
|
||||
In this field st_ored datetime
|
||||
In this field stored datetime
|
||||
|
||||
in: unixtime
|
||||
out: datetime
|
||||
|
|
|
|||
|
|
@ -58,9 +58,11 @@ class Message(base.TelegramObject):
|
|||
forward_from_message_id: base.Integer = fields.Field()
|
||||
forward_signature: base.String = fields.Field()
|
||||
forward_date: datetime.datetime = fields.DateTimeField()
|
||||
is_automatic_forward: base.Boolean = fields.Field()
|
||||
reply_to_message: Message = fields.Field(base="Message")
|
||||
via_bot: User = fields.Field(base=User)
|
||||
edit_date: datetime.datetime = fields.DateTimeField()
|
||||
has_protected_content: base.Boolean = fields.Field()
|
||||
media_group_id: base.String = fields.Field()
|
||||
author_signature: base.String = fields.Field()
|
||||
forward_sender_name: base.String = fields.Field()
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class HelperMode(Helper):
|
|||
@classmethod
|
||||
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:
|
||||
:return:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Welcome to aiogram's documentation!
|
|||
:target: https://pypi.python.org/pypi/aiogram
|
||||
: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
|
||||
: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