mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Merge branch 'dev-2.x' into add-new-ways-throttle
# Conflicts: # examples/throtling_example.py
This commit is contained in:
commit
ed0d4796e7
63 changed files with 1567 additions and 939 deletions
|
|
@ -38,5 +38,5 @@ __all__ = [
|
|||
'utils'
|
||||
]
|
||||
|
||||
__version__ = '2.2.1.dev1'
|
||||
__api_version__ = '4.3'
|
||||
__version__ = '2.3.dev1'
|
||||
__api_version__ = '4.4'
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class Methods(Helper):
|
|||
"""
|
||||
Helper for Telegram API Methods listed on https://core.telegram.org/bots/api
|
||||
|
||||
List is updated to Bot API 4.3
|
||||
List is updated to Bot API 4.4
|
||||
"""
|
||||
mode = HelperMode.lowerCamelCase
|
||||
|
||||
|
|
@ -182,6 +182,7 @@ class Methods(Helper):
|
|||
UNBAN_CHAT_MEMBER = Item() # unbanChatMember
|
||||
RESTRICT_CHAT_MEMBER = Item() # restrictChatMember
|
||||
PROMOTE_CHAT_MEMBER = Item() # promoteChatMember
|
||||
SET_CHAT_PERMISSIONS = Item() # setChatPermissions
|
||||
EXPORT_CHAT_INVITE_LINK = Item() # exportChatInviteLink
|
||||
SET_CHAT_PHOTO = Item() # setChatPhoto
|
||||
DELETE_CHAT_PHOTO = Item() # deleteChatPhoto
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
import warnings
|
||||
|
||||
from .base import BaseBot, api
|
||||
from .. import types
|
||||
|
|
@ -345,7 +346,6 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
prepare_file(payload, files, 'audio', audio)
|
||||
prepare_attachment(payload, files, 'thumb', thumb)
|
||||
|
||||
|
||||
result = await self.request(api.Methods.SEND_AUDIO, payload, files)
|
||||
return types.Message(**result)
|
||||
|
||||
|
|
@ -1016,6 +1016,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
|
||||
async def restrict_chat_member(self, chat_id: typing.Union[base.Integer, base.String],
|
||||
user_id: base.Integer,
|
||||
permissions: typing.Optional[types.ChatPermissions] = None,
|
||||
# permissions argument need to be required after removing other `can_*` arguments
|
||||
until_date: typing.Union[base.Integer, None] = None,
|
||||
can_send_messages: typing.Union[base.Boolean, None] = None,
|
||||
can_send_media_messages: typing.Union[base.Boolean, None] = None,
|
||||
|
|
@ -1032,6 +1034,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
|
||||
:param user_id: Unique identifier of the target user
|
||||
:type user_id: :obj:`base.Integer`
|
||||
:param permissions: New user permissions
|
||||
:type permissions: :obj:`ChatPermissions`
|
||||
:param until_date: Date when restrictions will be lifted for the user, unix time
|
||||
:type until_date: :obj:`typing.Union[base.Integer, None]`
|
||||
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
|
||||
|
|
@ -1049,8 +1053,19 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
until_date = prepare_arg(until_date)
|
||||
permissions = prepare_arg(permissions)
|
||||
payload = generate_payload(**locals())
|
||||
|
||||
for permission in ['can_send_messages',
|
||||
'can_send_media_messages',
|
||||
'can_send_other_messages',
|
||||
'can_add_web_page_previews']:
|
||||
if permission in payload:
|
||||
warnings.warn(f"The method `restrict_chat_member` now takes the new user permissions "
|
||||
f"in a single argument of the type ChatPermissions instead of "
|
||||
f"passing regular argument {payload[permission]}",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
|
||||
result = await self.request(api.Methods.RESTRICT_CHAT_MEMBER, payload)
|
||||
return result
|
||||
|
||||
|
|
@ -1101,6 +1116,25 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
result = await self.request(api.Methods.PROMOTE_CHAT_MEMBER, payload)
|
||||
return result
|
||||
|
||||
async def set_chat_permissions(self, chat_id: typing.Union[base.Integer, base.String],
|
||||
permissions: types.ChatPermissions) -> base.Boolean:
|
||||
"""
|
||||
Use this method to set default chat permissions for all members.
|
||||
The bot must be an administrator in the group or a supergroup for this to work and must have the
|
||||
can_restrict_members admin rights.
|
||||
|
||||
Returns True on success.
|
||||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target supergroup
|
||||
:param permissions: New default chat permissions
|
||||
:return: True on success.
|
||||
"""
|
||||
permissions = prepare_arg(permissions)
|
||||
payload = generate_payload(**locals())
|
||||
|
||||
result = await self.request(api.Methods.SET_CHAT_PERMISSIONS)
|
||||
return result
|
||||
|
||||
async def export_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String]) -> base.String:
|
||||
"""
|
||||
Use this method to generate a new invite link for a chat; any previously generated link is revoked.
|
||||
|
|
|
|||
200
aiogram/contrib/fsm_storage/mongo.py
Normal file
200
aiogram/contrib/fsm_storage/mongo.py
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
"""
|
||||
This module has mongo storage for finite-state machine
|
||||
based on `aiomongo <https://github.com/ZeoAlliance/aiomongo`_ driver
|
||||
"""
|
||||
|
||||
from typing import Union, Dict, Optional, List, Tuple, AnyStr
|
||||
|
||||
import aiomongo
|
||||
from aiomongo import AioMongoClient, Database
|
||||
|
||||
from ...dispatcher.storage import BaseStorage
|
||||
|
||||
STATE = 'aiogram_state'
|
||||
DATA = 'aiogram_data'
|
||||
BUCKET = 'aiogram_bucket'
|
||||
COLLECTIONS = (STATE, DATA, BUCKET)
|
||||
|
||||
|
||||
class MongoStorage(BaseStorage):
|
||||
"""
|
||||
Mongo-based storage for FSM.
|
||||
Usage:
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
storage = MongoStorage(host='localhost', port=27017, db_name='aiogram_fsm')
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
|
||||
And need to close Mongo client connections when shutdown
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
await dp.storage.close()
|
||||
await dp.storage.wait_closed()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, host='localhost', port=27017, db_name='aiogram_fsm',
|
||||
username=None, password=None, index=True, **kwargs):
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._db_name: str = db_name
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._kwargs = kwargs
|
||||
|
||||
self._mongo: Union[AioMongoClient, None] = None
|
||||
self._db: Union[Database, None] = None
|
||||
|
||||
self._index = index
|
||||
|
||||
async def get_client(self) -> AioMongoClient:
|
||||
if isinstance(self._mongo, AioMongoClient):
|
||||
return self._mongo
|
||||
|
||||
uri = 'mongodb://'
|
||||
|
||||
# set username + password
|
||||
if self._username and self._password:
|
||||
uri += f'{self._username}:{self._password}@'
|
||||
|
||||
# set host and port (optional)
|
||||
uri += f'{self._host}:{self._port}' if self._host else f'localhost:{self._port}'
|
||||
|
||||
# define and return client
|
||||
self._mongo = await aiomongo.create_client(uri)
|
||||
return self._mongo
|
||||
|
||||
async def get_db(self) -> Database:
|
||||
"""
|
||||
Get Mongo db
|
||||
|
||||
This property is awaitable.
|
||||
"""
|
||||
if isinstance(self._db, Database):
|
||||
return self._db
|
||||
|
||||
mongo = await self.get_client()
|
||||
self._db = mongo.get_database(self._db_name)
|
||||
|
||||
if self._index:
|
||||
await self.apply_index(self._db)
|
||||
return self._db
|
||||
|
||||
@staticmethod
|
||||
async def apply_index(db):
|
||||
for collection in COLLECTIONS:
|
||||
await db[collection].create_index(keys=[('chat', 1), ('user', 1)],
|
||||
name="chat_user_idx", unique=True, background=True)
|
||||
|
||||
async def close(self):
|
||||
if self._mongo:
|
||||
self._mongo.close()
|
||||
|
||||
async def wait_closed(self):
|
||||
if self._mongo:
|
||||
return await self._mongo.wait_closed()
|
||||
return True
|
||||
|
||||
async def set_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
state: Optional[AnyStr] = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
|
||||
if state is None:
|
||||
await db[STATE].delete_one(filter={'chat': chat, 'user': user})
|
||||
else:
|
||||
await db[STATE].update_one(filter={'chat': chat, 'user': user},
|
||||
update={'$set': {'state': state}}, upsert=True)
|
||||
|
||||
async def get_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
default: Optional[str] = None) -> Optional[str]:
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
result = await db[STATE].find_one(filter={'chat': chat, 'user': user})
|
||||
|
||||
return result.get('state') if result else default
|
||||
|
||||
async def set_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
data: Dict = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
|
||||
await db[DATA].update_one(filter={'chat': chat, 'user': user},
|
||||
update={'$set': {'data': data}}, upsert=True)
|
||||
|
||||
async def get_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
default: Optional[dict] = None) -> Dict:
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
result = await db[DATA].find_one(filter={'chat': chat, 'user': user})
|
||||
|
||||
return result.get('data') if result else default or {}
|
||||
|
||||
async def update_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
data: Dict = None, **kwargs):
|
||||
if data is None:
|
||||
data = {}
|
||||
temp_data = await self.get_data(chat=chat, user=user, default={})
|
||||
temp_data.update(data, **kwargs)
|
||||
await self.set_data(chat=chat, user=user, data=temp_data)
|
||||
|
||||
def has_bucket(self):
|
||||
return True
|
||||
|
||||
async def get_bucket(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
default: Optional[dict] = None) -> Dict:
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
result = await db[BUCKET].find_one(filter={'chat': chat, 'user': user})
|
||||
return result.get('bucket') if result else default or {}
|
||||
|
||||
async def set_bucket(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
|
||||
bucket: Dict = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
db = await self.get_db()
|
||||
|
||||
await db[BUCKET].update_one(filter={'chat': chat, 'user': user},
|
||||
update={'$set': {'bucket': bucket}}, upsert=True)
|
||||
|
||||
async def update_bucket(self, *, chat: Union[str, int, None] = None,
|
||||
user: Union[str, int, None] = None,
|
||||
bucket: Dict = None, **kwargs):
|
||||
if bucket is None:
|
||||
bucket = {}
|
||||
temp_bucket = await self.get_bucket(chat=chat, user=user)
|
||||
temp_bucket.update(bucket, **kwargs)
|
||||
await self.set_bucket(chat=chat, user=user, bucket=temp_bucket)
|
||||
|
||||
async def reset_all(self, full=True):
|
||||
"""
|
||||
Reset states in DB
|
||||
|
||||
:param full: clean DB or clean only states
|
||||
:return:
|
||||
"""
|
||||
db = await self.get_db()
|
||||
|
||||
await db[STATE].drop()
|
||||
|
||||
if full:
|
||||
await db[DATA].drop()
|
||||
await db[BUCKET].drop()
|
||||
|
||||
async def get_states_list(self) -> List[Tuple[int, int]]:
|
||||
"""
|
||||
Get list of all stored chat's and user's
|
||||
|
||||
:return: list of tuples where first element is chat id and second is user id
|
||||
"""
|
||||
db = await self.get_db()
|
||||
result = []
|
||||
|
||||
items = await db[STATE].find().to_list()
|
||||
for item in items:
|
||||
result.append(
|
||||
(int(item['chat']), int(item['user']))
|
||||
)
|
||||
|
||||
return result
|
||||
|
|
@ -97,15 +97,13 @@ class I18nMiddleware(BaseMiddleware):
|
|||
if locale not in self.locales:
|
||||
if n is 1:
|
||||
return singular
|
||||
else:
|
||||
return plural
|
||||
return plural
|
||||
|
||||
translator = self.locales[locale]
|
||||
|
||||
if plural is None:
|
||||
return translator.gettext(singular)
|
||||
else:
|
||||
return translator.ngettext(singular, plural, n)
|
||||
return translator.ngettext(singular, plural, n)
|
||||
|
||||
def lazy_gettext(self, singular, plural=None, n=1, locale=None, enable_cache=True) -> LazyProxy:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import aiohttp
|
|||
from aiohttp.helpers import sentinel
|
||||
|
||||
from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \
|
||||
RegexpCommandsFilter, StateFilter, Text, IdFilter
|
||||
RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter
|
||||
from .handler import Handler
|
||||
from .middlewares import MiddlewareManager
|
||||
from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \
|
||||
|
|
@ -85,39 +85,64 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
|||
|
||||
filters_factory.bind(StateFilter, exclude_event_handlers=[
|
||||
self.errors_handlers,
|
||||
self.poll_handlers
|
||||
self.poll_handlers,
|
||||
])
|
||||
filters_factory.bind(ContentTypeFilter, event_handlers=[
|
||||
self.message_handlers, self.edited_message_handlers,
|
||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
]),
|
||||
filters_factory.bind(Command, event_handlers=[
|
||||
self.message_handlers, self.edited_message_handlers
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers
|
||||
])
|
||||
filters_factory.bind(Text, event_handlers=[
|
||||
self.message_handlers, self.edited_message_handlers,
|
||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers, self.poll_handlers, self.inline_query_handlers
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers,
|
||||
self.poll_handlers,
|
||||
self.inline_query_handlers,
|
||||
])
|
||||
filters_factory.bind(HashTag, event_handlers=[
|
||||
self.message_handlers, self.edited_message_handlers,
|
||||
self.channel_post_handlers, self.edited_channel_post_handlers
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
])
|
||||
filters_factory.bind(Regexp, event_handlers=[
|
||||
self.message_handlers, self.edited_message_handlers,
|
||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers, self.poll_handlers, self.inline_query_handlers
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers,
|
||||
self.poll_handlers,
|
||||
self.inline_query_handlers,
|
||||
])
|
||||
filters_factory.bind(RegexpCommandsFilter, event_handlers=[
|
||||
self.message_handlers, self.edited_message_handlers
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
])
|
||||
filters_factory.bind(ExceptionsFilter, event_handlers=[
|
||||
self.errors_handlers
|
||||
self.errors_handlers,
|
||||
])
|
||||
filters_factory.bind(IdFilter, event_handlers=[
|
||||
self.message_handlers, self.edited_message_handlers,
|
||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers, self.inline_query_handlers
|
||||
filters_factory.bind(AdminFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers,
|
||||
self.inline_query_handlers,
|
||||
])
|
||||
filters_factory.bind(IDFilter, event_handlers=[
|
||||
self.message_handlers,
|
||||
self.edited_message_handlers,
|
||||
self.channel_post_handlers,
|
||||
self.edited_channel_post_handlers,
|
||||
self.callback_query_handlers,
|
||||
self.inline_query_handlers,
|
||||
])
|
||||
|
||||
def __del__(self):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \
|
||||
ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, Text, IdFilter
|
||||
ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter
|
||||
from .factory import FiltersFactory
|
||||
from .filters import AbstractFilter, BoundFilter, Filter, FilterNotPassed, FilterRecord, execute_filter, \
|
||||
check_filters, get_filter_spec, get_filters_spec
|
||||
|
|
@ -23,9 +23,10 @@ __all__ = [
|
|||
'Regexp',
|
||||
'StateFilter',
|
||||
'Text',
|
||||
'IdFilter',
|
||||
'IDFilter',
|
||||
'AdminFilter',
|
||||
'get_filter_spec',
|
||||
'get_filters_spec',
|
||||
'execute_filter',
|
||||
'check_filters'
|
||||
'check_filters',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from babel.support import LazyProxy
|
|||
|
||||
from aiogram import types
|
||||
from aiogram.dispatcher.filters.filters import BoundFilter, Filter
|
||||
from aiogram.types import CallbackQuery, Message, InlineQuery, Poll
|
||||
from aiogram.types import CallbackQuery, Message, InlineQuery, Poll, ChatType
|
||||
|
||||
|
||||
class Command(Filter):
|
||||
|
|
@ -84,9 +84,9 @@ class Command(Filter):
|
|||
|
||||
if not ignore_mention and mention and (await message.bot.me).username.lower() != mention.lower():
|
||||
return False
|
||||
elif prefix not in prefixes:
|
||||
if prefix not in prefixes:
|
||||
return False
|
||||
elif (command.lower() if ignore_case else command) not in commands:
|
||||
if (command.lower() if ignore_case else command) not in commands:
|
||||
return False
|
||||
|
||||
return {'command': Command.CommandObj(command=command, prefix=prefix, mention=mention)}
|
||||
|
|
@ -149,7 +149,7 @@ class CommandStart(Command):
|
|||
|
||||
:param deep_link: string or compiled regular expression (by ``re.compile(...)``).
|
||||
"""
|
||||
super(CommandStart, self).__init__(['start'])
|
||||
super().__init__(['start'])
|
||||
self.deep_link = deep_link
|
||||
|
||||
async def check(self, message: types.Message):
|
||||
|
|
@ -159,7 +159,7 @@ class CommandStart(Command):
|
|||
:param message:
|
||||
:return:
|
||||
"""
|
||||
check = await super(CommandStart, self).check(message)
|
||||
check = await super().check(message)
|
||||
|
||||
if check and self.deep_link is not None:
|
||||
if not isinstance(self.deep_link, re.Pattern):
|
||||
|
|
@ -179,7 +179,7 @@ class CommandHelp(Command):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CommandHelp, self).__init__(['help'])
|
||||
super().__init__(['help'])
|
||||
|
||||
|
||||
class CommandSettings(Command):
|
||||
|
|
@ -188,7 +188,7 @@ class CommandSettings(Command):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CommandSettings, self).__init__(['settings'])
|
||||
super().__init__(['settings'])
|
||||
|
||||
|
||||
class CommandPrivacy(Command):
|
||||
|
|
@ -197,7 +197,7 @@ class CommandPrivacy(Command):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CommandPrivacy, self).__init__(['privacy'])
|
||||
super().__init__(['privacy'])
|
||||
|
||||
|
||||
class Text(Filter):
|
||||
|
|
@ -205,19 +205,27 @@ class Text(Filter):
|
|||
Simple text filter
|
||||
"""
|
||||
|
||||
_default_params = (
|
||||
('text', 'equals'),
|
||||
('text_contains', 'contains'),
|
||||
('text_startswith', 'startswith'),
|
||||
('text_endswith', 'endswith'),
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
equals: Optional[Union[str, LazyProxy]] = None,
|
||||
contains: Optional[Union[str, LazyProxy]] = None,
|
||||
startswith: Optional[Union[str, LazyProxy]] = None,
|
||||
endswith: Optional[Union[str, LazyProxy]] = None,
|
||||
equals: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||
contains: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||
startswith: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||
endswith: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||
ignore_case=False):
|
||||
"""
|
||||
Check text for one of pattern. Only one mode can be used in one filter.
|
||||
In every pattern, a single string is treated as a list with 1 element.
|
||||
|
||||
:param equals:
|
||||
:param contains:
|
||||
:param startswith:
|
||||
:param endswith:
|
||||
:param equals: True if object's text in the list
|
||||
:param contains: True if object's text contains all strings from the list
|
||||
:param startswith: True if object's text starts with any of strings from the list
|
||||
:param endswith: True if object's text ends with any of strings from the list
|
||||
:param ignore_case: case insensitive
|
||||
"""
|
||||
# Only one mode can be used. check it.
|
||||
|
|
@ -232,6 +240,9 @@ class Text(Filter):
|
|||
elif check == 0:
|
||||
raise ValueError(f"No one mode is specified!")
|
||||
|
||||
equals, contains, endswith, startswith = map(lambda e: [e] if isinstance(e, str) or isinstance(e, LazyProxy)
|
||||
else e,
|
||||
(equals, contains, endswith, startswith))
|
||||
self.equals = equals
|
||||
self.contains = contains
|
||||
self.endswith = endswith
|
||||
|
|
@ -240,14 +251,9 @@ class Text(Filter):
|
|||
|
||||
@classmethod
|
||||
def validate(cls, full_config: Dict[str, Any]):
|
||||
if 'text' in full_config:
|
||||
return {'equals': full_config.pop('text')}
|
||||
elif 'text_contains' in full_config:
|
||||
return {'contains': full_config.pop('text_contains')}
|
||||
elif 'text_startswith' in full_config:
|
||||
return {'startswith': full_config.pop('text_startswith')}
|
||||
elif 'text_endswith' in full_config:
|
||||
return {'endswith': full_config.pop('text_endswith')}
|
||||
for param, key in cls._default_params:
|
||||
if param in full_config:
|
||||
return {key: full_config.pop(param)}
|
||||
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, Poll]):
|
||||
if isinstance(obj, Message):
|
||||
|
|
@ -265,27 +271,26 @@ class Text(Filter):
|
|||
|
||||
if self.ignore_case:
|
||||
text = text.lower()
|
||||
_pre_process_func = lambda s: str(s).lower()
|
||||
else:
|
||||
_pre_process_func = str
|
||||
|
||||
# now check
|
||||
if self.equals is not None:
|
||||
self.equals = str(self.equals)
|
||||
if self.ignore_case:
|
||||
self.equals = self.equals.lower()
|
||||
return text == self.equals
|
||||
elif self.contains is not None:
|
||||
self.contains = str(self.contains)
|
||||
if self.ignore_case:
|
||||
self.contains = self.contains.lower()
|
||||
return self.contains in text
|
||||
elif self.startswith is not None:
|
||||
self.startswith = str(self.startswith)
|
||||
if self.ignore_case:
|
||||
self.startswith = self.startswith.lower()
|
||||
return text.startswith(self.startswith)
|
||||
elif self.endswith is not None:
|
||||
self.endswith = str(self.endswith)
|
||||
if self.ignore_case:
|
||||
self.endswith = self.endswith.lower()
|
||||
return text.endswith(self.endswith)
|
||||
equals = list(map(_pre_process_func, self.equals))
|
||||
return text in equals
|
||||
|
||||
if self.contains is not None:
|
||||
contains = list(map(_pre_process_func, self.contains))
|
||||
return all(map(text.__contains__, contains))
|
||||
|
||||
if self.startswith is not None:
|
||||
startswith = list(map(_pre_process_func, self.startswith))
|
||||
return any(map(text.startswith, startswith))
|
||||
|
||||
if self.endswith is not None:
|
||||
endswith = list(map(_pre_process_func, self.endswith))
|
||||
return any(map(text.endswith, endswith))
|
||||
|
||||
return False
|
||||
|
||||
|
|
@ -505,7 +510,7 @@ class ExceptionsFilter(BoundFilter):
|
|||
return False
|
||||
|
||||
|
||||
class IdFilter(Filter):
|
||||
class IDFilter(Filter):
|
||||
|
||||
def __init__(self,
|
||||
user_id: Optional[Union[Iterable[Union[int, str]], str, int]] = None,
|
||||
|
|
@ -560,9 +565,63 @@ class IdFilter(Filter):
|
|||
|
||||
if self.user_id and self.chat_id:
|
||||
return user_id in self.user_id and chat_id in self.chat_id
|
||||
elif self.user_id:
|
||||
if self.user_id:
|
||||
return user_id in self.user_id
|
||||
elif self.chat_id:
|
||||
if self.chat_id:
|
||||
return chat_id in self.chat_id
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class AdminFilter(Filter):
|
||||
"""
|
||||
Checks if user is admin in a chat.
|
||||
If is_chat_admin is not set, the filter will check in the current chat (correct only for messages).
|
||||
is_chat_admin is required for InlineQuery.
|
||||
"""
|
||||
|
||||
def __init__(self, is_chat_admin: Optional[Union[Iterable[Union[int, str]], str, int, bool]] = None):
|
||||
self._check_current = False
|
||||
self._chat_ids = None
|
||||
|
||||
if is_chat_admin is False:
|
||||
raise ValueError("is_chat_admin cannot be False")
|
||||
|
||||
if is_chat_admin:
|
||||
if isinstance(is_chat_admin, bool):
|
||||
self._check_current = is_chat_admin
|
||||
if isinstance(is_chat_admin, Iterable):
|
||||
self._chat_ids = list(is_chat_admin)
|
||||
else:
|
||||
self._chat_ids = [is_chat_admin]
|
||||
else:
|
||||
self._check_current = True
|
||||
|
||||
@classmethod
|
||||
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
|
||||
result = {}
|
||||
|
||||
if "is_chat_admin" in full_config:
|
||||
result["is_chat_admin"] = full_config.pop("is_chat_admin")
|
||||
|
||||
return result
|
||||
|
||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]) -> bool:
|
||||
user_id = obj.from_user.id
|
||||
|
||||
if self._check_current:
|
||||
if isinstance(obj, Message):
|
||||
message = obj
|
||||
elif isinstance(obj, CallbackQuery) and obj.message:
|
||||
message = obj.message
|
||||
else:
|
||||
return False
|
||||
if ChatType.is_private(message): # there is no admin in private chats
|
||||
return False
|
||||
chat_ids = [message.chat.id]
|
||||
else:
|
||||
chat_ids = self._chat_ids
|
||||
|
||||
admins = [member.user.id for chat_id in chat_ids for member in await obj.bot.get_chat_administrators(chat_id)]
|
||||
|
||||
return user_id in admins
|
||||
|
|
|
|||
|
|
@ -70,4 +70,4 @@ class FiltersFactory:
|
|||
yield filter_
|
||||
|
||||
if full_config:
|
||||
raise NameError('Invalid filter name(s): \'' + '\', '.join(full_config.keys()) + '\'')
|
||||
raise NameError("Invalid filter name(s): '" + "', ".join(full_config.keys()) + "'")
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class FilterRecord:
|
|||
Filters record for factory
|
||||
"""
|
||||
|
||||
def __init__(self, callback: typing.Callable,
|
||||
def __init__(self, callback: typing.Union[typing.Callable, 'AbstractFilter'],
|
||||
validator: typing.Optional[typing.Callable] = None,
|
||||
event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
|
||||
exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None):
|
||||
|
|
@ -202,14 +202,14 @@ class BoundFilter(Filter):
|
|||
You need to implement ``__init__`` method with single argument related with key attribute
|
||||
and ``check`` method where you need to implement filter logic.
|
||||
"""
|
||||
|
||||
"""Unique name of the filter argument. You need to override this attribute."""
|
||||
|
||||
key = None
|
||||
"""If :obj:`True` this filter will be added to the all of the registered handlers"""
|
||||
"""Unique name of the filter argument. You need to override this attribute."""
|
||||
required = False
|
||||
"""Default value for configure required filters"""
|
||||
"""If :obj:`True` this filter will be added to the all of the registered handlers"""
|
||||
default = None
|
||||
|
||||
"""Default value for configure required filters"""
|
||||
|
||||
@classmethod
|
||||
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -25,17 +25,17 @@ class State:
|
|||
|
||||
@property
|
||||
def state(self):
|
||||
if self._state is None:
|
||||
return None
|
||||
elif self._state == '*':
|
||||
if self._state is None or self._state == '*':
|
||||
return self._state
|
||||
elif self._group_name is None and self._group:
|
||||
|
||||
if self._group_name is None and self._group:
|
||||
group = self._group.__full_group_name__
|
||||
elif self._group_name:
|
||||
group = self._group_name
|
||||
else:
|
||||
group = '@'
|
||||
return f"{group}:{self._state}"
|
||||
|
||||
return f'{group}:{self._state}'
|
||||
|
||||
def set_parent(self, group):
|
||||
if not issubclass(group, StatesGroup):
|
||||
|
|
@ -73,7 +73,6 @@ class StatesGroupMeta(type):
|
|||
elif inspect.isclass(prop) and issubclass(prop, StatesGroup):
|
||||
childs.append(prop)
|
||||
prop._parent = cls
|
||||
# continue
|
||||
|
||||
cls._parent = None
|
||||
cls._childs = tuple(childs)
|
||||
|
|
@ -83,13 +82,13 @@ class StatesGroupMeta(type):
|
|||
return cls
|
||||
|
||||
@property
|
||||
def __group_name__(cls):
|
||||
def __group_name__(cls) -> str:
|
||||
return cls._group_name
|
||||
|
||||
@property
|
||||
def __full_group_name__(cls):
|
||||
def __full_group_name__(cls) -> str:
|
||||
if cls._parent:
|
||||
return cls._parent.__full_group_name__ + '.' + cls._group_name
|
||||
return '.'.join((cls._parent.__full_group_name__, cls._group_name))
|
||||
return cls._group_name
|
||||
|
||||
@property
|
||||
|
|
@ -97,7 +96,7 @@ class StatesGroupMeta(type):
|
|||
return cls._states
|
||||
|
||||
@property
|
||||
def childs(cls):
|
||||
def childs(cls) -> tuple:
|
||||
return cls._childs
|
||||
|
||||
@property
|
||||
|
|
@ -130,9 +129,9 @@ class StatesGroupMeta(type):
|
|||
def __contains__(cls, item):
|
||||
if isinstance(item, str):
|
||||
return item in cls.all_states_names
|
||||
elif isinstance(item, State):
|
||||
if isinstance(item, State):
|
||||
return item in cls.all_states
|
||||
elif isinstance(item, StatesGroup):
|
||||
if isinstance(item, StatesGroup):
|
||||
return item in cls.all_childs
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import inspect
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Iterable
|
||||
from typing import Optional, Iterable, List
|
||||
|
||||
ctx_data = ContextVar('ctx_handler_data')
|
||||
current_handler = ContextVar('current_handler')
|
||||
|
|
@ -25,9 +25,8 @@ class CancelHandler(Exception):
|
|||
def _get_spec(func: callable):
|
||||
while hasattr(func, '__wrapped__'): # Try to resolve decorated callbacks
|
||||
func = func.__wrapped__
|
||||
|
||||
spec = inspect.getfullargspec(func)
|
||||
return spec, func
|
||||
return spec
|
||||
|
||||
|
||||
def _check_spec(spec: inspect.FullArgSpec, kwargs: dict):
|
||||
|
|
@ -42,11 +41,10 @@ class Handler:
|
|||
self.dispatcher = dispatcher
|
||||
self.once = once
|
||||
|
||||
self.handlers = []
|
||||
self.handlers: List[Handler.HandlerObj] = []
|
||||
self.middleware_key = middleware_key
|
||||
|
||||
def register(self, handler, filters=None, index=None):
|
||||
from .filters import get_filters_spec
|
||||
"""
|
||||
Register callback
|
||||
|
||||
|
|
@ -56,7 +54,9 @@ class Handler:
|
|||
:param filters: list of filters
|
||||
:param index: you can reorder handlers
|
||||
"""
|
||||
spec, handler = _get_spec(handler)
|
||||
from .filters import get_filters_spec
|
||||
|
||||
spec = _get_spec(handler)
|
||||
|
||||
if filters and not isinstance(filters, (list, tuple, set)):
|
||||
filters = [filters]
|
||||
|
|
|
|||
|
|
@ -523,7 +523,7 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio
|
|||
'disable_web_page_preview': self.disable_web_page_preview,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
def write(self, *text, sep=' '):
|
||||
|
|
@ -642,7 +642,7 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'caption': self.caption,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -704,7 +704,7 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'title': self.title,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -817,7 +817,7 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'caption': self.caption,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -871,7 +871,7 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'duration': self.duration,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -924,7 +924,7 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'length': self.length,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1050,7 +1050,7 @@ class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'longitude': self.longitude,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1109,7 +1109,7 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'foursquare_id': self.foursquare_id,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1160,7 +1160,7 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'last_name': self.last_name,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1220,7 +1220,7 @@ class KickChatMember(BaseResponse):
|
|||
return {
|
||||
'chat_id': self.chat_id,
|
||||
'user_id': self.user_id,
|
||||
'until_date': prepare_arg(self.until_date)
|
||||
'until_date': prepare_arg(self.until_date),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1608,7 +1608,7 @@ class EditMessageText(BaseResponse, ParseModeMixin, DisableWebPagePreviewMixin):
|
|||
'text': self.text,
|
||||
'parse_mode': self.parse_mode,
|
||||
'disable_web_page_preview': self.disable_web_page_preview,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1649,7 +1649,7 @@ class EditMessageCaption(BaseResponse):
|
|||
'message_id': self.message_id,
|
||||
'inline_message_id': self.inline_message_id,
|
||||
'caption': self.caption,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1685,7 +1685,7 @@ class EditMessageReplyMarkup(BaseResponse):
|
|||
'chat_id': self.chat_id,
|
||||
'message_id': self.message_id,
|
||||
'inline_message_id': self.inline_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1756,7 +1756,7 @@ class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'sticker': self.sticker,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1848,7 +1848,7 @@ class AddStickerToSet(BaseResponse):
|
|||
'name': self.name,
|
||||
'png_sticker': self.png_sticker,
|
||||
'emojis': self.emojis,
|
||||
'mask_position': prepare_arg(self.mask_position)
|
||||
'mask_position': prepare_arg(self.mask_position),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2177,5 +2177,5 @@ class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
|||
'game_short_name': self.game_short_name,
|
||||
'disable_notification': self.disable_notification,
|
||||
'reply_to_message_id': self.reply_to_message_id,
|
||||
'reply_markup': prepare_arg(self.reply_markup)
|
||||
'reply_markup': prepare_arg(self.reply_markup),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from .callback_game import CallbackGame
|
|||
from .callback_query import CallbackQuery
|
||||
from .chat import Chat, ChatActions, ChatType
|
||||
from .chat_member import ChatMember, ChatMemberStatus
|
||||
from .chat_permissions import ChatPermissions
|
||||
from .chat_photo import ChatPhoto
|
||||
from .chosen_inline_result import ChosenInlineResult
|
||||
from .contact import Contact
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import typing
|
|||
|
||||
from . import base
|
||||
from . import fields
|
||||
from .chat_permissions import ChatPermissions
|
||||
from .chat_photo import ChatPhoto
|
||||
from ..utils import helper
|
||||
from ..utils import markdown
|
||||
|
|
@ -27,6 +28,7 @@ class Chat(base.TelegramObject):
|
|||
description: base.String = fields.Field()
|
||||
invite_link: base.String = fields.Field()
|
||||
pinned_message: 'Message' = fields.Field(base='Message')
|
||||
permissions: ChatPermissions = fields.Field(base=ChatPermissions)
|
||||
sticker_set_name: base.String = fields.Field()
|
||||
can_set_sticker_set: base.Boolean = fields.Field()
|
||||
|
||||
|
|
@ -202,6 +204,7 @@ class Chat(base.TelegramObject):
|
|||
return await self.bot.unban_chat_member(self.id, user_id=user_id)
|
||||
|
||||
async def restrict(self, user_id: base.Integer,
|
||||
permissions: typing.Optional[ChatPermissions] = None,
|
||||
until_date: typing.Union[base.Integer, None] = None,
|
||||
can_send_messages: typing.Union[base.Boolean, None] = None,
|
||||
can_send_media_messages: typing.Union[base.Boolean, None] = None,
|
||||
|
|
@ -216,6 +219,8 @@ class Chat(base.TelegramObject):
|
|||
|
||||
:param user_id: Unique identifier of the target user
|
||||
:type user_id: :obj:`base.Integer`
|
||||
:param permissions: New user permissions
|
||||
:type permissions: :obj:`ChatPermissions`
|
||||
:param until_date: Date when restrictions will be lifted for the user, unix time.
|
||||
:type until_date: :obj:`typing.Union[base.Integer, None]`
|
||||
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
|
||||
|
|
@ -232,7 +237,9 @@ class Chat(base.TelegramObject):
|
|||
:return: Returns True on success.
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
return await self.bot.restrict_chat_member(self.id, user_id=user_id, until_date=until_date,
|
||||
return await self.bot.restrict_chat_member(self.id, user_id=user_id,
|
||||
permissions=permissions,
|
||||
until_date=until_date,
|
||||
can_send_messages=can_send_messages,
|
||||
can_send_media_messages=can_send_media_messages,
|
||||
can_send_other_messages=can_send_other_messages,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import datetime
|
||||
import warnings
|
||||
from typing import Optional
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
|
|
@ -28,22 +29,17 @@ class ChatMember(base.TelegramObject):
|
|||
is_member: base.Boolean = fields.Field()
|
||||
can_send_messages: base.Boolean = fields.Field()
|
||||
can_send_media_messages: base.Boolean = fields.Field()
|
||||
can_send_polls: base.Boolean = fields.Field()
|
||||
can_send_other_messages: base.Boolean = fields.Field()
|
||||
can_add_web_page_previews: base.Boolean = fields.Field()
|
||||
|
||||
def is_admin(self):
|
||||
warnings.warn('`is_admin` method deprecated due to updates in Bot API 4.2. '
|
||||
'This method renamed to `is_chat_admin` and will be available until aiogram 2.3',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return self.is_chat_admin()
|
||||
def is_chat_admin(self) -> bool:
|
||||
return ChatMemberStatus.is_chat_admin(self.status)
|
||||
|
||||
def is_chat_admin(self):
|
||||
return ChatMemberStatus.is_admin(self.status)
|
||||
def is_chat_member(self) -> bool:
|
||||
return ChatMemberStatus.is_chat_member(self.status)
|
||||
|
||||
def is_chat_member(self):
|
||||
return ChatMemberStatus.is_member(self.status)
|
||||
|
||||
def __int__(self):
|
||||
def __int__(self) -> int:
|
||||
return self.user.id
|
||||
|
||||
|
||||
|
|
@ -51,33 +47,19 @@ class ChatMemberStatus(helper.Helper):
|
|||
"""
|
||||
Chat member status
|
||||
"""
|
||||
|
||||
mode = helper.HelperMode.lowercase
|
||||
|
||||
CREATOR = helper.Item() # creator
|
||||
ADMINISTRATOR = helper.Item() # administrator
|
||||
MEMBER = helper.Item() # member
|
||||
RESTRICTED = helper.Item() # restricted
|
||||
LEFT = helper.Item() # left
|
||||
KICKED = helper.Item() # kicked
|
||||
|
||||
@classmethod
|
||||
def is_admin(cls, role):
|
||||
warnings.warn('`is_admin` method deprecated due to updates in Bot API 4.2. '
|
||||
'This method renamed to `is_chat_admin` and will be available until aiogram 2.3',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return cls.is_chat_admin(role)
|
||||
|
||||
@classmethod
|
||||
def is_member(cls, role):
|
||||
warnings.warn('`is_member` method deprecated due to updates in Bot API 4.2. '
|
||||
'This method renamed to `is_chat_member` and will be available until aiogram 2.3',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return cls.is_chat_member(role)
|
||||
|
||||
@classmethod
|
||||
def is_chat_admin(cls, role):
|
||||
def is_chat_admin(cls, role: str) -> bool:
|
||||
return role in [cls.ADMINISTRATOR, cls.CREATOR]
|
||||
|
||||
@classmethod
|
||||
def is_chat_member(cls, role):
|
||||
return role in [cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR]
|
||||
def is_chat_member(cls, role: str) -> bool:
|
||||
return role in [cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR, cls.RESTRICTED]
|
||||
|
|
|
|||
39
aiogram/types/chat_permissions.py
Normal file
39
aiogram/types/chat_permissions.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from . import base
|
||||
from . import fields
|
||||
|
||||
|
||||
class ChatPermissions(base.TelegramObject):
|
||||
"""
|
||||
Describes actions that a non-administrator user is allowed to take in a chat.
|
||||
|
||||
https://core.telegram.org/bots/api#chatpermissions
|
||||
"""
|
||||
can_send_messages: base.Boolean = fields.Field()
|
||||
can_send_media_messages: base.Boolean = fields.Field()
|
||||
can_send_polls: base.Boolean = fields.Field()
|
||||
can_send_other_messages: base.Boolean = fields.Field()
|
||||
can_add_web_page_previews: 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()
|
||||
|
||||
def __init__(self,
|
||||
can_send_messages: base.Boolean = None,
|
||||
can_send_media_messages: base.Boolean = None,
|
||||
can_send_polls: base.Boolean = None,
|
||||
can_send_other_messages: base.Boolean = None,
|
||||
can_add_web_page_previews: base.Boolean = None,
|
||||
can_change_info: base.Boolean = None,
|
||||
can_invite_users: base.Boolean = None,
|
||||
can_pin_messages: base.Boolean = None,
|
||||
**kwargs):
|
||||
super(ChatPermissions, self).__init__(
|
||||
can_send_messages=can_send_messages,
|
||||
can_send_media_messages=can_send_media_messages,
|
||||
can_send_polls=can_send_polls,
|
||||
can_send_other_messages=can_send_other_messages,
|
||||
can_add_web_page_previews=can_add_web_page_previews,
|
||||
can_change_info=can_change_info,
|
||||
can_invite_users=can_invite_users,
|
||||
can_pin_messages=can_pin_messages,
|
||||
)
|
||||
|
|
@ -94,60 +94,60 @@ class Message(base.TelegramObject):
|
|||
def content_type(self):
|
||||
if self.text:
|
||||
return ContentType.TEXT
|
||||
elif self.audio:
|
||||
if self.audio:
|
||||
return ContentType.AUDIO
|
||||
elif self.animation:
|
||||
if self.animation:
|
||||
return ContentType.ANIMATION
|
||||
elif self.document:
|
||||
if self.document:
|
||||
return ContentType.DOCUMENT
|
||||
elif self.game:
|
||||
if self.game:
|
||||
return ContentType.GAME
|
||||
elif self.photo:
|
||||
if self.photo:
|
||||
return ContentType.PHOTO
|
||||
elif self.sticker:
|
||||
if self.sticker:
|
||||
return ContentType.STICKER
|
||||
elif self.video:
|
||||
if self.video:
|
||||
return ContentType.VIDEO
|
||||
elif self.video_note:
|
||||
if self.video_note:
|
||||
return ContentType.VIDEO_NOTE
|
||||
elif self.voice:
|
||||
if self.voice:
|
||||
return ContentType.VOICE
|
||||
elif self.contact:
|
||||
if self.contact:
|
||||
return ContentType.CONTACT
|
||||
elif self.venue:
|
||||
if self.venue:
|
||||
return ContentType.VENUE
|
||||
elif self.location:
|
||||
if self.location:
|
||||
return ContentType.LOCATION
|
||||
elif self.new_chat_members:
|
||||
if self.new_chat_members:
|
||||
return ContentType.NEW_CHAT_MEMBERS
|
||||
elif self.left_chat_member:
|
||||
if self.left_chat_member:
|
||||
return ContentType.LEFT_CHAT_MEMBER
|
||||
elif self.invoice:
|
||||
if self.invoice:
|
||||
return ContentType.INVOICE
|
||||
elif self.successful_payment:
|
||||
if self.successful_payment:
|
||||
return ContentType.SUCCESSFUL_PAYMENT
|
||||
elif self.connected_website:
|
||||
if self.connected_website:
|
||||
return ContentType.CONNECTED_WEBSITE
|
||||
elif self.migrate_from_chat_id:
|
||||
if self.migrate_from_chat_id:
|
||||
return ContentType.MIGRATE_FROM_CHAT_ID
|
||||
elif self.migrate_to_chat_id:
|
||||
if self.migrate_to_chat_id:
|
||||
return ContentType.MIGRATE_TO_CHAT_ID
|
||||
elif self.pinned_message:
|
||||
if self.pinned_message:
|
||||
return ContentType.PINNED_MESSAGE
|
||||
elif self.new_chat_title:
|
||||
if self.new_chat_title:
|
||||
return ContentType.NEW_CHAT_TITLE
|
||||
elif self.new_chat_photo:
|
||||
if self.new_chat_photo:
|
||||
return ContentType.NEW_CHAT_PHOTO
|
||||
elif self.delete_chat_photo:
|
||||
if self.delete_chat_photo:
|
||||
return ContentType.DELETE_CHAT_PHOTO
|
||||
elif self.group_chat_created:
|
||||
if self.group_chat_created:
|
||||
return ContentType.GROUP_CHAT_CREATED
|
||||
elif self.passport_data:
|
||||
if self.passport_data:
|
||||
return ContentType.PASSPORT_DATA
|
||||
elif self.poll:
|
||||
if self.poll:
|
||||
return ContentType.POLL
|
||||
else:
|
||||
return ContentType.UNKNOWN
|
||||
|
||||
return ContentType.UNKNOWN
|
||||
|
||||
def is_command(self):
|
||||
"""
|
||||
|
|
@ -959,70 +959,6 @@ class Message(base.TelegramObject):
|
|||
reply_to_message_id=self.message_id if reply else None,
|
||||
reply_markup=reply_markup)
|
||||
|
||||
async def send_animation(self, animation: typing.Union[base.InputFile, base.String],
|
||||
duration: typing.Union[base.Integer, None] = None,
|
||||
width: typing.Union[base.Integer, None] = None,
|
||||
height: typing.Union[base.Integer, None] = None,
|
||||
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
|
||||
caption: typing.Union[base.String, None] = None,
|
||||
parse_mode: typing.Union[base.String, None] = None,
|
||||
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||
reply_markup: typing.Union[InlineKeyboardMarkup,
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
ForceReply, None] = None,
|
||||
reply: base.Boolean = True) -> Message:
|
||||
"""
|
||||
Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound).
|
||||
|
||||
On success, the sent Message is returned.
|
||||
Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future.
|
||||
|
||||
Source https://core.telegram.org/bots/api#sendanimation
|
||||
|
||||
:param animation: Animation to send. Pass a file_id as String to send an animation that exists
|
||||
on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation
|
||||
from the Internet, or upload a new animation using multipart/form-data
|
||||
:type animation: :obj:`typing.Union[base.InputFile, base.String]`
|
||||
:param duration: Duration of sent animation in seconds
|
||||
:type duration: :obj:`typing.Union[base.Integer, None]`
|
||||
:param width: Animation width
|
||||
:type width: :obj:`typing.Union[base.Integer, None]`
|
||||
:param height: Animation height
|
||||
:type height: :obj:`typing.Union[base.Integer, None]`
|
||||
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
|
||||
A thumbnail‘s width and height should not exceed 90.
|
||||
:type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
|
||||
:param caption: Animation caption (may also be used when resending animation by file_id), 0-1024 characters
|
||||
:type caption: :obj:`typing.Union[base.String, None]`
|
||||
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
|
||||
fixed-width text or inline URLs in the media caption
|
||||
:type parse_mode: :obj:`typing.Union[base.String, None]`
|
||||
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
|
||||
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||
: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
|
||||
:type reply_markup: :obj:`typing.Union[typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup,
|
||||
types.ReplyKeyboardRemove, types.ForceReply], None]`
|
||||
:param reply: fill 'reply_to_message_id'
|
||||
:return: On success, the sent Message is returned
|
||||
:rtype: :obj:`types.Message`
|
||||
"""
|
||||
warn_deprecated('"Message.send_animation" method will be removed in 2.3 version.\n'
|
||||
'Use "Message.reply_animation" instead.',
|
||||
stacklevel=8)
|
||||
|
||||
return await self.bot.send_animation(self.chat.id,
|
||||
animation=animation,
|
||||
duration=duration,
|
||||
width=width,
|
||||
height=height,
|
||||
thumb=thumb,
|
||||
caption=caption,
|
||||
parse_mode=parse_mode,
|
||||
disable_notification=disable_notification,
|
||||
reply_to_message_id=self.message_id if reply else None,
|
||||
reply_markup=reply_markup)
|
||||
|
||||
async def reply_animation(self, animation: typing.Union[base.InputFile, base.String],
|
||||
duration: typing.Union[base.Integer, None] = None,
|
||||
|
|
@ -1323,55 +1259,6 @@ class Message(base.TelegramObject):
|
|||
reply_to_message_id=self.message_id if reply else None,
|
||||
reply_markup=reply_markup)
|
||||
|
||||
async def send_venue(self,
|
||||
latitude: base.Float, longitude: base.Float,
|
||||
title: base.String, address: base.String,
|
||||
foursquare_id: typing.Union[base.String, None] = None,
|
||||
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||
reply_markup: typing.Union[InlineKeyboardMarkup,
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
ForceReply, None] = None,
|
||||
reply: base.Boolean = True) -> Message:
|
||||
"""
|
||||
Use this method to send information about a venue.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#sendvenue
|
||||
|
||||
:param latitude: Latitude of the venue
|
||||
:type latitude: :obj:`base.Float`
|
||||
:param longitude: Longitude of the venue
|
||||
:type longitude: :obj:`base.Float`
|
||||
:param title: Name of the venue
|
||||
:type title: :obj:`base.String`
|
||||
:param address: Address of the venue
|
||||
:type address: :obj:`base.String`
|
||||
:param foursquare_id: Foursquare identifier of the venue
|
||||
:type foursquare_id: :obj:`typing.Union[base.String, None]`
|
||||
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||
: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
|
||||
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||
:param reply: fill 'reply_to_message_id'
|
||||
:return: On success, the sent Message is returned.
|
||||
:rtype: :obj:`types.Message`
|
||||
"""
|
||||
warn_deprecated('"Message.send_venue" method will be removed in 2.3 version.\n'
|
||||
'Use "Message.reply_venue" instead.',
|
||||
stacklevel=8)
|
||||
|
||||
return await self.bot.send_venue(chat_id=self.chat.id,
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
title=title,
|
||||
address=address,
|
||||
foursquare_id=foursquare_id,
|
||||
disable_notification=disable_notification,
|
||||
reply_to_message_id=self.message_id if reply else None,
|
||||
reply_markup=reply_markup)
|
||||
|
||||
async def reply_venue(self,
|
||||
latitude: base.Float, longitude: base.Float,
|
||||
title: base.String, address: base.String,
|
||||
|
|
@ -1417,46 +1304,6 @@ class Message(base.TelegramObject):
|
|||
reply_to_message_id=self.message_id if reply else None,
|
||||
reply_markup=reply_markup)
|
||||
|
||||
async def send_contact(self, phone_number: base.String,
|
||||
first_name: base.String, last_name: typing.Union[base.String, None] = None,
|
||||
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||
reply_markup: typing.Union[InlineKeyboardMarkup,
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
ForceReply, None] = None,
|
||||
reply: base.Boolean = True) -> Message:
|
||||
"""
|
||||
Use this method to send phone contacts.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#sendcontact
|
||||
|
||||
:param phone_number: Contact's phone number
|
||||
:type phone_number: :obj:`base.String`
|
||||
:param first_name: Contact's first name
|
||||
:type first_name: :obj:`base.String`
|
||||
:param last_name: Contact's last name
|
||||
:type last_name: :obj:`typing.Union[base.String, None]`
|
||||
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||
:type disable_notification: :obj:`typing.Union[base.Boolean, None]`
|
||||
: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
|
||||
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||
:param reply: fill 'reply_to_message_id'
|
||||
:return: On success, the sent Message is returned.
|
||||
:rtype: :obj:`types.Message`
|
||||
"""
|
||||
warn_deprecated('"Message.send_contact" method will be removed in 2.3 version.\n'
|
||||
'Use "Message.reply_contact" instead.',
|
||||
stacklevel=8)
|
||||
|
||||
return await self.bot.send_contact(chat_id=self.chat.id,
|
||||
phone_number=phone_number,
|
||||
first_name=first_name, last_name=last_name,
|
||||
disable_notification=disable_notification,
|
||||
reply_to_message_id=self.message_id if reply else None,
|
||||
reply_markup=reply_markup)
|
||||
|
||||
async def reply_contact(self, phone_number: base.String,
|
||||
first_name: base.String, last_name: typing.Union[base.String, None] = None,
|
||||
disable_notification: typing.Union[base.Boolean, None] = None,
|
||||
|
|
|
|||
|
|
@ -49,30 +49,24 @@ class MessageEntity(base.TelegramObject):
|
|||
entity_text = self.get_text(text)
|
||||
|
||||
if self.type == MessageEntityType.BOLD:
|
||||
if as_html:
|
||||
return markdown.hbold(entity_text)
|
||||
return markdown.bold(entity_text)
|
||||
elif self.type == MessageEntityType.ITALIC:
|
||||
if as_html:
|
||||
return markdown.hitalic(entity_text)
|
||||
return markdown.italic(entity_text)
|
||||
elif self.type == MessageEntityType.PRE:
|
||||
if as_html:
|
||||
return markdown.hpre(entity_text)
|
||||
return markdown.pre(entity_text)
|
||||
elif self.type == MessageEntityType.CODE:
|
||||
if as_html:
|
||||
return markdown.hcode(entity_text)
|
||||
return markdown.code(entity_text)
|
||||
elif self.type == MessageEntityType.URL:
|
||||
if as_html:
|
||||
return markdown.hlink(entity_text, entity_text)
|
||||
return markdown.link(entity_text, entity_text)
|
||||
elif self.type == MessageEntityType.TEXT_LINK:
|
||||
if as_html:
|
||||
return markdown.hlink(entity_text, self.url)
|
||||
return markdown.link(entity_text, self.url)
|
||||
elif self.type == MessageEntityType.TEXT_MENTION and self.user:
|
||||
method = markdown.hbold if as_html else markdown.bold
|
||||
return method(entity_text)
|
||||
if self.type == MessageEntityType.ITALIC:
|
||||
method = markdown.hitalic if as_html else markdown.italic
|
||||
return method(entity_text)
|
||||
if self.type == MessageEntityType.PRE:
|
||||
method = markdown.hpre if as_html else markdown.pre
|
||||
return method(entity_text)
|
||||
if self.type == MessageEntityType.CODE:
|
||||
method = markdown.hcode if as_html else markdown.code
|
||||
return method(entity_text)
|
||||
if self.type == MessageEntityType.URL:
|
||||
method = markdown.hlink if as_html else markdown.link
|
||||
return method(entity_text, entity_text)
|
||||
if self.type == MessageEntityType.TEXT_LINK:
|
||||
method = markdown.hlink if as_html else markdown.link
|
||||
return method(entity_text, self.url)
|
||||
if self.type == MessageEntityType.TEXT_MENTION and self.user:
|
||||
return self.user.get_mention(entity_text)
|
||||
return entity_text
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class Sticker(base.TelegramObject, mixins.Downloadable):
|
|||
file_id: base.String = fields.Field()
|
||||
width: base.Integer = fields.Field()
|
||||
height: base.Integer = fields.Field()
|
||||
is_animated: base.Boolean = fields.Field()
|
||||
thumb: PhotoSize = fields.Field(base=PhotoSize)
|
||||
emoji: base.String = fields.Field()
|
||||
set_name: base.String = fields.Field()
|
||||
|
|
|
|||
|
|
@ -13,5 +13,6 @@ class StickerSet(base.TelegramObject):
|
|||
"""
|
||||
name: base.String = fields.Field()
|
||||
title: base.String = fields.Field()
|
||||
is_animated: base.Boolean = fields.Field()
|
||||
contains_masks: base.Boolean = fields.Field()
|
||||
stickers: typing.List[Sticker] = fields.ListField(base=Sticker)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import babel
|
||||
|
||||
from . import base
|
||||
|
|
@ -45,7 +47,7 @@ class User(base.TelegramObject):
|
|||
return self.full_name
|
||||
|
||||
@property
|
||||
def locale(self) -> babel.core.Locale or None:
|
||||
def locale(self) -> Optional[babel.core.Locale]:
|
||||
"""
|
||||
Get user's locale
|
||||
|
||||
|
|
|
|||
|
|
@ -28,13 +28,13 @@ class CallbackData:
|
|||
|
||||
def __init__(self, prefix, *parts, sep=':'):
|
||||
if not isinstance(prefix, str):
|
||||
raise TypeError(f"Prefix must be instance of str not {type(prefix).__name__}")
|
||||
elif not prefix:
|
||||
raise ValueError('Prefix can\'t be empty')
|
||||
elif sep in prefix:
|
||||
raise ValueError(f"Separator '{sep}' can't be used in prefix")
|
||||
elif not parts:
|
||||
raise TypeError('Parts is not passed!')
|
||||
raise TypeError(f'Prefix must be instance of str not {type(prefix).__name__}')
|
||||
if not prefix:
|
||||
raise ValueError("Prefix can't be empty")
|
||||
if sep in prefix:
|
||||
raise ValueError(f"Separator {sep!r} can't be used in prefix")
|
||||
if not parts:
|
||||
raise TypeError('Parts were not passed!')
|
||||
|
||||
self.prefix = prefix
|
||||
self.sep = sep
|
||||
|
|
@ -59,20 +59,20 @@ class CallbackData:
|
|||
if args:
|
||||
value = args.pop(0)
|
||||
else:
|
||||
raise ValueError(f"Value for '{part}' is not passed!")
|
||||
raise ValueError(f'Value for {part!r} was not passed!')
|
||||
|
||||
if value is not None and not isinstance(value, str):
|
||||
value = str(value)
|
||||
|
||||
if not value:
|
||||
raise ValueError(f"Value for part {part} can't be empty!'")
|
||||
elif self.sep in value:
|
||||
raise ValueError(f"Symbol defined as separator can't be used in values of parts")
|
||||
raise ValueError(f"Value for part {part!r} can't be empty!'")
|
||||
if self.sep in value:
|
||||
raise ValueError(f"Symbol {self.sep!r} is defined as the separator and can't be used in parts' values")
|
||||
|
||||
data.append(value)
|
||||
|
||||
if args or kwargs:
|
||||
raise TypeError('Too many arguments is passed!')
|
||||
raise TypeError('Too many arguments were passed!')
|
||||
|
||||
callback_data = self.sep.join(data)
|
||||
if len(callback_data) > 64:
|
||||
|
|
@ -106,30 +106,31 @@ class CallbackData:
|
|||
"""
|
||||
for key in config.keys():
|
||||
if key not in self._part_names:
|
||||
raise ValueError(f"Invalid field name '{key}'")
|
||||
raise ValueError(f'Invalid field name {key!r}')
|
||||
return CallbackDataFilter(self, config)
|
||||
|
||||
|
||||
class CallbackDataFilter(Filter):
|
||||
|
||||
def __init__(self, factory: CallbackData, config: typing.Dict[str, str]):
|
||||
self.config = config
|
||||
self.factory = factory
|
||||
|
||||
@classmethod
|
||||
def validate(cls, full_config: typing.Dict[str, typing.Any]):
|
||||
raise ValueError('That filter can\'t be used in filters factory!')
|
||||
raise ValueError("That filter can't be used in filters factory!")
|
||||
|
||||
async def check(self, query: types.CallbackQuery):
|
||||
try:
|
||||
data = self.factory.parse(query.data)
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
for key, value in self.config.items():
|
||||
if isinstance(value, (list, tuple, set)):
|
||||
if data.get(key) not in value:
|
||||
return False
|
||||
else:
|
||||
if value != data.get(key):
|
||||
return False
|
||||
return {'callback_data': data}
|
||||
|
||||
for key, value in self.config.items():
|
||||
if isinstance(value, (list, tuple, set, frozenset)):
|
||||
if data.get(key) not in value:
|
||||
return False
|
||||
else:
|
||||
if data.get(key) != value:
|
||||
return False
|
||||
return {'callback_data': data}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
"""
|
||||
Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
|
||||
"""
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import warnings
|
||||
import asyncio
|
||||
|
||||
|
||||
def deprecated(reason):
|
||||
|
|
@ -12,6 +9,8 @@ def deprecated(reason):
|
|||
This is a decorator which can be used to mark functions
|
||||
as deprecated. It will result in a warning being emitted
|
||||
when the function is used.
|
||||
|
||||
Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
|
||||
"""
|
||||
|
||||
if isinstance(reason, str):
|
||||
|
|
@ -41,7 +40,7 @@ def deprecated(reason):
|
|||
|
||||
return decorator
|
||||
|
||||
elif inspect.isclass(reason) or inspect.isfunction(reason):
|
||||
if inspect.isclass(reason) or inspect.isfunction(reason):
|
||||
|
||||
# The @deprecated is used without any 'reason'.
|
||||
#
|
||||
|
|
@ -65,11 +64,71 @@ def deprecated(reason):
|
|||
|
||||
return wrapper1
|
||||
|
||||
else:
|
||||
raise TypeError(repr(type(reason)))
|
||||
raise TypeError(repr(type(reason)))
|
||||
|
||||
|
||||
def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2):
|
||||
warnings.simplefilter('always', warning)
|
||||
warnings.warn(message, category=warning, stacklevel=stacklevel)
|
||||
warnings.simplefilter('default', warning)
|
||||
|
||||
|
||||
def renamed_argument(old_name: str, new_name: str, until_version: str, stacklevel: int = 3):
|
||||
"""
|
||||
A meta-decorator to mark an argument as deprecated.
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
@renamed_argument("chat", "chat_id", "3.0") # stacklevel=3 by default
|
||||
@renamed_argument("user", "user_id", "3.0", stacklevel=4)
|
||||
def some_function(user_id, chat_id=None):
|
||||
print(f"user_id={user_id}, chat_id={chat_id}")
|
||||
|
||||
some_function(user=123) # prints 'user_id=123, chat_id=None' with warning
|
||||
some_function(123) # prints 'user_id=123, chat_id=None' without warning
|
||||
some_function(user_id=123) # prints 'user_id=123, chat_id=None' without warning
|
||||
|
||||
|
||||
:param old_name:
|
||||
:param new_name:
|
||||
:param until_version: the version in which the argument is scheduled to be removed
|
||||
:param stacklevel: leave it to default if it's the first decorator used.
|
||||
Increment with any new decorator used.
|
||||
:return: decorator
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
@functools.wraps(func)
|
||||
async def wrapped(*args, **kwargs):
|
||||
if old_name in kwargs:
|
||||
warn_deprecated(f"In coroutine '{func.__name__}' argument '{old_name}' "
|
||||
f"is renamed to '{new_name}' "
|
||||
f"and will be removed in aiogram {until_version}",
|
||||
stacklevel=stacklevel)
|
||||
kwargs.update(
|
||||
{
|
||||
new_name: kwargs[old_name],
|
||||
}
|
||||
)
|
||||
kwargs.pop(old_name)
|
||||
await func(*args, **kwargs)
|
||||
else:
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kwargs):
|
||||
if old_name in kwargs:
|
||||
warn_deprecated(f"In function `{func.__name__}` argument `{old_name}` "
|
||||
f"is renamed to `{new_name}` "
|
||||
f"and will be removed in aiogram {until_version}",
|
||||
stacklevel=stacklevel)
|
||||
kwargs.update(
|
||||
{
|
||||
new_name: kwargs[old_name],
|
||||
}
|
||||
)
|
||||
kwargs.pop(old_name)
|
||||
func(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
return decorator
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from ..dispatcher.webhook import BOT_DISPATCHER_KEY, DEFAULT_ROUTE_NAME, Webhook
|
|||
APP_EXECUTOR_KEY = 'APP_EXECUTOR'
|
||||
|
||||
|
||||
def _setup_callbacks(executor, on_startup=None, on_shutdown=None):
|
||||
def _setup_callbacks(executor: 'Executor', on_startup=None, on_shutdown=None):
|
||||
if on_startup is not None:
|
||||
executor.on_startup(on_startup)
|
||||
if on_shutdown is not None:
|
||||
|
|
@ -23,7 +23,7 @@ def _setup_callbacks(executor, on_startup=None, on_shutdown=None):
|
|||
|
||||
|
||||
def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=True,
|
||||
on_startup=None, on_shutdown=None, timeout=20, fast=True):
|
||||
on_startup=None, on_shutdown=None, timeout=20, relax=0.1, fast=True):
|
||||
"""
|
||||
Start bot in long-polling mode
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=Tr
|
|||
executor = Executor(dispatcher, skip_updates=skip_updates, loop=loop)
|
||||
_setup_callbacks(executor, on_startup, on_shutdown)
|
||||
|
||||
executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, fast=fast)
|
||||
executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, relax=relax, fast=fast)
|
||||
|
||||
|
||||
def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||
|
|
@ -291,7 +291,7 @@ class Executor:
|
|||
self.set_webhook(webhook_path=webhook_path, request_handler=request_handler, route_name=route_name)
|
||||
self.run_app(**kwargs)
|
||||
|
||||
def start_polling(self, reset_webhook=None, timeout=20, fast=True):
|
||||
def start_polling(self, reset_webhook=None, timeout=20, relax=0.1, fast=True):
|
||||
"""
|
||||
Start bot in long-polling mode
|
||||
|
||||
|
|
@ -303,7 +303,8 @@ class Executor:
|
|||
|
||||
try:
|
||||
loop.run_until_complete(self._startup_polling())
|
||||
loop.create_task(self.dispatcher.start_polling(reset_webhook=reset_webhook, timeout=timeout, fast=fast))
|
||||
loop.create_task(self.dispatcher.start_polling(reset_webhook=reset_webhook, timeout=timeout,
|
||||
relax=relax, fast=fast))
|
||||
loop.run_forever()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
# loop.stop()
|
||||
|
|
@ -339,7 +340,7 @@ class Executor:
|
|||
async def _skip_updates(self):
|
||||
await self.dispatcher.reset_webhook(True)
|
||||
await self.dispatcher.skip_updates()
|
||||
log.warning(f"Updates are skipped successfully.")
|
||||
log.warning(f'Updates were skipped successfully.')
|
||||
|
||||
async def _welcome(self):
|
||||
user = await self.dispatcher.bot.me
|
||||
|
|
|
|||
|
|
@ -120,15 +120,15 @@ class HelperMode(Helper):
|
|||
"""
|
||||
if mode == cls.SCREAMING_SNAKE_CASE:
|
||||
return cls._screaming_snake_case(text)
|
||||
elif mode == cls.snake_case:
|
||||
if mode == cls.snake_case:
|
||||
return cls._snake_case(text)
|
||||
elif mode == cls.lowercase:
|
||||
if mode == cls.lowercase:
|
||||
return cls._snake_case(text).replace('_', '')
|
||||
elif mode == cls.lowerCamelCase:
|
||||
if mode == cls.lowerCamelCase:
|
||||
return cls._camel_case(text)
|
||||
elif mode == cls.CamelCase:
|
||||
if mode == cls.CamelCase:
|
||||
return cls._camel_case(text, True)
|
||||
elif callable(mode):
|
||||
if callable(mode):
|
||||
return mode(text)
|
||||
return text
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ T = TypeVar('T')
|
|||
|
||||
class ContextInstanceMixin:
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
cls.__context_instance = contextvars.ContextVar('instance_' + cls.__name__)
|
||||
cls.__context_instance = contextvars.ContextVar(f'instance_{cls.__name__}')
|
||||
return cls
|
||||
|
||||
@classmethod
|
||||
|
|
@ -43,5 +43,5 @@ class ContextInstanceMixin:
|
|||
@classmethod
|
||||
def set_current(cls: Type[T], value: T):
|
||||
if not isinstance(value, cls):
|
||||
raise TypeError(f"Value should be instance of '{cls.__name__}' not '{type(value).__name__}'")
|
||||
raise TypeError(f'Value should be instance of {cls.__name__!r} not {type(value).__name__!r}')
|
||||
cls.__context_instance.set(value)
|
||||
|
|
|
|||
|
|
@ -52,14 +52,14 @@ def prepare_arg(value):
|
|||
"""
|
||||
if value is None:
|
||||
return value
|
||||
elif isinstance(value, (list, dict)) or hasattr(value, 'to_python'):
|
||||
if isinstance(value, (list, dict)) or hasattr(value, 'to_python'):
|
||||
return json.dumps(_normalize(value))
|
||||
elif isinstance(value, datetime.timedelta):
|
||||
if isinstance(value, datetime.timedelta):
|
||||
now = datetime.datetime.now()
|
||||
return int((now + value).timestamp())
|
||||
elif isinstance(value, datetime.datetime):
|
||||
if isinstance(value, datetime.datetime):
|
||||
return round(value.timestamp())
|
||||
elif isinstance(value, LazyProxy):
|
||||
if isinstance(value, LazyProxy):
|
||||
return str(value)
|
||||
return value
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue