From bd032f066e514e67d578e1cd0fcc91371ff4a3bc Mon Sep 17 00:00:00 2001 From: Arslan 'Ars2014' Sakhapov Date: Thu, 8 Aug 2019 19:12:49 +0500 Subject: [PATCH 1/6] Added AdminFilter and example of its usage --- aiogram/dispatcher/dispatcher.py | 7 ++++- aiogram/dispatcher/filters/__init__.py | 3 +- aiogram/dispatcher/filters/builtin.py | 40 ++++++++++++++++++++++++++ examples/admin_filter_example.py | 28 ++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 examples/admin_filter_example.py diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 0da5f621..6e3aacc5 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -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, \ @@ -119,6 +119,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin): 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 + ]) def __del__(self): self.stop_polling() diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py index eb4a5a52..b9174ee6 100644 --- a/aiogram/dispatcher/filters/__init__.py +++ b/aiogram/dispatcher/filters/__init__.py @@ -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 @@ -24,6 +24,7 @@ __all__ = [ 'StateFilter', 'Text', 'IdFilter', + 'AdminFilter', 'get_filter_spec', 'get_filters_spec', 'execute_filter', diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index df72b9d0..4401f349 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -562,3 +562,43 @@ class IdFilter(Filter): return chat_id in self.chat_id return False + + +class AdminFilter(Filter): + """ + Checks if user is admin in a chat. + If chat_id is not set, the filter will check in the current chat (correct only for messages). + chat_id is required for InlineQuery. + """ + + def __init__(self, admin_chat_id: typing.Optional[int] = None, admin_current_chat: typing.Optional[bool] = False): + self.chat_id = admin_chat_id + self.check_current_chat = admin_current_chat + + @classmethod + def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]: + result = {} + + if "admin_chat_id" in full_config: # use prefix 'admin' to not conflict with IdFilter + result["admin_chat_id"] = full_config.pop("admin_chat_id") + if "admin_current_chat" in full_config: # set True if need to check current chat + result["admin_current_chat"] = full_config.pop("admin_current_chat") + + return result + + async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]) -> bool: + user_id = obj.from_user.id + chat_id = self.chat_id + + if not chat_id or self.check_current_chat: + if isinstance(obj, Message): + chat_id = obj.chat.id + elif isinstance(obj, CallbackQuery): + if obj.message: + chat_id = obj.message.chat.id + else: + raise ValueError("Cannot get current chat in a InlineQuery") + + admins = [member.user.id for member in await obj.bot.get_chat_administrators(chat_id)] + + return user_id in admins diff --git a/examples/admin_filter_example.py b/examples/admin_filter_example.py new file mode 100644 index 00000000..052f8efe --- /dev/null +++ b/examples/admin_filter_example.py @@ -0,0 +1,28 @@ +import logging + +from aiogram import Bot, Dispatcher, types, executor + +API_TOKEN = 'API TOKEN HERE' + +logging.basicConfig(level=logging.DEBUG) + +bot = Bot(token=API_TOKEN) +dp = Dispatcher(bot=bot) + +chat_id = -1001241113577 + + +# checks specified chat +@dp.message_handler(admin_chat_id=chat_id) +async def handler(msg: types.Message): + await msg.reply(f"You are an admin of the chat '{chat_id}'", reply=False) + + +# checks current chat +@dp.message_handler(admin_current_chat=True) +async def handler(msg: types.Message): + await msg.reply("You are an admin of the current chat!", reply=False) + + +if __name__ == '__main__': + executor.start_polling(dp) From 9f6b577f081a88ff5917166775b1b53ffcc07fcd Mon Sep 17 00:00:00 2001 From: Arslan 'Ars2014' Sakhapov Date: Thu, 8 Aug 2019 19:14:27 +0500 Subject: [PATCH 2/6] Rename IdFilter to IDFilter Rename IdFilter to IDFilter as it is an abbreviation like URL --- aiogram/dispatcher/filters/builtin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index 4401f349..65dd595c 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -501,7 +501,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, From c0e60f87063397bb014841a04306ce24980b76da Mon Sep 17 00:00:00 2001 From: Arslan 'Ars2014' Sakhapov Date: Thu, 8 Aug 2019 20:53:52 +0500 Subject: [PATCH 3/6] Fix issues and rework AdminFilter --- aiogram/dispatcher/dispatcher.py | 4 +-- aiogram/dispatcher/filters/__init__.py | 4 +-- aiogram/dispatcher/filters/builtin.py | 44 ++++++++++++++++++-------- docs/source/dispatcher/filters.rst | 12 +++++-- examples/admin_filter_example.py | 23 ++++++++------ 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 6e3aacc5..150e8019 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -9,7 +9,7 @@ import aiohttp from aiohttp.helpers import sentinel from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \ - RegexpCommandsFilter, StateFilter, Text, IdFilter, AdminFilter + RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter from .handler import Handler from .middlewares import MiddlewareManager from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \ @@ -114,7 +114,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin): filters_factory.bind(ExceptionsFilter, event_handlers=[ self.errors_handlers ]) - filters_factory.bind(IdFilter, event_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 diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py index b9174ee6..3f202915 100644 --- a/aiogram/dispatcher/filters/__init__.py +++ b/aiogram/dispatcher/filters/__init__.py @@ -1,5 +1,5 @@ from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \ - ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, Text, IdFilter, AdminFilter + 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,7 +23,7 @@ __all__ = [ 'Regexp', 'StateFilter', 'Text', - 'IdFilter', + 'IDFilter', 'AdminFilter', 'get_filter_spec', 'get_filters_spec', diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index 65dd595c..c84ddca4 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -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): @@ -571,34 +571,50 @@ class AdminFilter(Filter): chat_id is required for InlineQuery. """ - def __init__(self, admin_chat_id: typing.Optional[int] = None, admin_current_chat: typing.Optional[bool] = False): - self.chat_id = admin_chat_id - self.check_current_chat = admin_current_chat + def __init__(self, is_chat_admin: Optional[Union[Iterable[Union[int, str]], str, int, bool]] = None): + self._all_chats = 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._all_chats = is_chat_admin + if isinstance(is_chat_admin, Iterable): + self.chat_ids = list(map(int, is_chat_admin)) + else: + self.chat_ids = [int(is_chat_admin)] + else: + self._all_chats = True @classmethod def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]: result = {} - if "admin_chat_id" in full_config: # use prefix 'admin' to not conflict with IdFilter - result["admin_chat_id"] = full_config.pop("admin_chat_id") - if "admin_current_chat" in full_config: # set True if need to check current chat - result["admin_current_chat"] = full_config.pop("admin_current_chat") + 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 - chat_id = self.chat_id + chat_ids = None + + if self._all_chats: + if ChatType.is_private(obj): # there is no admin in private chats + return False - if not chat_id or self.check_current_chat: if isinstance(obj, Message): - chat_id = obj.chat.id + chat_ids = [obj.chat.id] elif isinstance(obj, CallbackQuery): if obj.message: - chat_id = obj.message.chat.id + chat_ids = [obj.message.chat.id] else: - raise ValueError("Cannot get current chat in a InlineQuery") + return False + else: + chat_ids = self.chat_ids - admins = [member.user.id for member in await obj.bot.get_chat_administrators(chat_id)] + 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 diff --git a/docs/source/dispatcher/filters.rst b/docs/source/dispatcher/filters.rst index af03e163..059a4f06 100644 --- a/docs/source/dispatcher/filters.rst +++ b/docs/source/dispatcher/filters.rst @@ -111,10 +111,18 @@ ExceptionsFilter :show-inheritance: -IdFilter +IDFilter ---------------- -.. autoclass:: aiogram.dispatcher.filters.builtin.IdFilter +.. autoclass:: aiogram.dispatcher.filters.builtin.IDFilter + :members: + :show-inheritance: + + +AdminFilter +---------------- + +.. autoclass:: aiogram.dispatcher.filters.builtin.AdminFilter :members: :show-inheritance: diff --git a/examples/admin_filter_example.py b/examples/admin_filter_example.py index 052f8efe..ec8746bb 100644 --- a/examples/admin_filter_example.py +++ b/examples/admin_filter_example.py @@ -2,26 +2,31 @@ import logging from aiogram import Bot, Dispatcher, types, executor -API_TOKEN = 'API TOKEN HERE' +API_TOKEN = 'API_TOKEN_HERE' + logging.basicConfig(level=logging.DEBUG) bot = Bot(token=API_TOKEN) dp = Dispatcher(bot=bot) -chat_id = -1001241113577 - # checks specified chat -@dp.message_handler(admin_chat_id=chat_id) -async def handler(msg: types.Message): - await msg.reply(f"You are an admin of the chat '{chat_id}'", reply=False) +@dp.message_handler(is_chat_admin=-1001241113577) +async def handle_specified(msg: types.Message): + await msg.answer("You are an admin of the specified chat!") + + +# checks multiple chats +@dp.message_handler(is_chat_admin=[-1001241113577, -320463906]) +async def handle_multiple(msg: types.Message): + await msg.answer("You are an admin of multiple chats!") # checks current chat -@dp.message_handler(admin_current_chat=True) -async def handler(msg: types.Message): - await msg.reply("You are an admin of the current chat!", reply=False) +@dp.message_handler(is_chat_admin=True) +async def handler3(msg: types.Message): + await msg.answer("You are an admin of the current chat!") if __name__ == '__main__': From c7c27e9e1c81ef8d9c3474dff33f49757fed3011 Mon Sep 17 00:00:00 2001 From: Arslan 'Ars2014' Sakhapov Date: Thu, 8 Aug 2019 21:07:23 +0500 Subject: [PATCH 4/6] Some fixes --- aiogram/dispatcher/filters/builtin.py | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index c84ddca4..9270932c 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -572,21 +572,21 @@ class AdminFilter(Filter): """ def __init__(self, is_chat_admin: Optional[Union[Iterable[Union[int, str]], str, int, bool]] = None): - self._all_chats = False - self.chat_ids = 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._all_chats = is_chat_admin + self._check_current = is_chat_admin if isinstance(is_chat_admin, Iterable): - self.chat_ids = list(map(int, is_chat_admin)) + self._chat_ids = list(map(int, is_chat_admin)) else: - self.chat_ids = [int(is_chat_admin)] + self._chat_ids = [int(is_chat_admin)] else: - self._all_chats = True + self._check_current = True @classmethod def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]: @@ -601,19 +601,19 @@ class AdminFilter(Filter): user_id = obj.from_user.id chat_ids = None - if self._all_chats: - if ChatType.is_private(obj): # there is no admin in private chats - return False - + if self._check_current: if isinstance(obj, Message): + if ChatType.is_private(obj): + return False chat_ids = [obj.chat.id] - elif isinstance(obj, CallbackQuery): - if obj.message: - chat_ids = [obj.message.chat.id] + elif isinstance(obj, CallbackQuery) and obj.message: + if ChatType.is_private(obj.message): # there is no admin in private chats + return False + chat_ids = [obj.message.chat.id] else: return False else: - chat_ids = self.chat_ids + 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)] From de0f8d33f24ce36a494f27028b053344d685c8e3 Mon Sep 17 00:00:00 2001 From: Arslan 'Ars2014' Sakhapov Date: Thu, 8 Aug 2019 21:15:01 +0500 Subject: [PATCH 5/6] Doc fix --- aiogram/dispatcher/filters/builtin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index 9270932c..acba3a2c 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -567,8 +567,8 @@ class IDFilter(Filter): class AdminFilter(Filter): """ Checks if user is admin in a chat. - If chat_id is not set, the filter will check in the current chat (correct only for messages). - chat_id is required for InlineQuery. + 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): From bda0bdd0131c6edcfd62dcb79c5a14a07810c0ea Mon Sep 17 00:00:00 2001 From: Arslan 'Ars2014' Sakhapov Date: Fri, 9 Aug 2019 12:57:32 +0500 Subject: [PATCH 6/6] Optimizations from code review --- aiogram/dispatcher/filters/builtin.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py index acba3a2c..54490a35 100644 --- a/aiogram/dispatcher/filters/builtin.py +++ b/aiogram/dispatcher/filters/builtin.py @@ -582,9 +582,9 @@ class AdminFilter(Filter): if isinstance(is_chat_admin, bool): self._check_current = is_chat_admin if isinstance(is_chat_admin, Iterable): - self._chat_ids = list(map(int, is_chat_admin)) + self._chat_ids = list(is_chat_admin) else: - self._chat_ids = [int(is_chat_admin)] + self._chat_ids = [is_chat_admin] else: self._check_current = True @@ -599,19 +599,17 @@ class AdminFilter(Filter): async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]) -> bool: user_id = obj.from_user.id - chat_ids = None if self._check_current: if isinstance(obj, Message): - if ChatType.is_private(obj): - return False - chat_ids = [obj.chat.id] + message = obj elif isinstance(obj, CallbackQuery) and obj.message: - if ChatType.is_private(obj.message): # there is no admin in private chats - return False - chat_ids = [obj.message.chat.id] + 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