diff --git a/src/altlinux/altlinux/__main__.py b/src/altlinux/altlinux/__main__.py index d99d89e..c99c3f4 100644 --- a/src/altlinux/altlinux/__main__.py +++ b/src/altlinux/altlinux/__main__.py @@ -10,7 +10,9 @@ async def main(): [ module_loader("standard", "config", safe=False), module_loader("standard", "command_helper"), - # module_loader("standard", "report"), + # safe=False из-за super().__init__() + module_loader("standard", "filters", safe=False), + module_loader("standard", "report"), ] ) await ocab.start() diff --git a/src/ocab_modules/ocab_modules/standard/admin/info.json b/src/ocab_modules/ocab_modules/standard/admin/info.json index b2d01d3..a3eae7a 100644 --- a/src/ocab_modules/ocab_modules/standard/admin/info.json +++ b/src/ocab_modules/ocab_modules/standard/admin/info.json @@ -7,7 +7,8 @@ "privileged": false, "dependencies": { "required": { - "standard.filters": "^1.0.0" + "standard.filters": "^1.0.0", + "standard.roles": "^1.0.0" } } } diff --git a/src/ocab_modules/ocab_modules/standard/command_helper/__init__.py b/src/ocab_modules/ocab_modules/standard/command_helper/__init__.py index 9b3ee14..90596da 100644 --- a/src/ocab_modules/ocab_modules/standard/command_helper/__init__.py +++ b/src/ocab_modules/ocab_modules/standard/command_helper/__init__.py @@ -1 +1 @@ -from .main import module_init, register_command +from .main import module_late_init, register_command diff --git a/src/ocab_modules/ocab_modules/standard/command_helper/info.json b/src/ocab_modules/ocab_modules/standard/command_helper/info.json index dda6c9e..00a14dc 100644 --- a/src/ocab_modules/ocab_modules/standard/command_helper/info.json +++ b/src/ocab_modules/ocab_modules/standard/command_helper/info.json @@ -5,10 +5,5 @@ "author": "OCAB Team", "version": "1.0.0", "privileged": false, - "dependencies": { - "required": { - "standard.roles": "^1.0.0", - "standard.database": "^1.0.0" - } - } + "dependencies": {} } diff --git a/src/ocab_modules/ocab_modules/standard/command_helper/main.py b/src/ocab_modules/ocab_modules/standard/command_helper/main.py index 37f78e6..d33d1ba 100644 --- a/src/ocab_modules/ocab_modules/standard/command_helper/main.py +++ b/src/ocab_modules/ocab_modules/standard/command_helper/main.py @@ -1,28 +1,11 @@ -from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict - -from aiogram import BaseMiddleware -from aiogram.types import BotCommand, TelegramObject +from aiogram.types import BotCommand from ocab_core.modules_system.public_api import ( - get_module, - register_outer_message_middleware, - set_my_commands, + set_my_commands, log ) -if TYPE_CHECKING: - from ocab_modules.standard.database import db_api as IDbApi - from ocab_modules.standard.roles import Roles as IRoles - commands = dict() -db_api: "IDbApi" = get_module( - "standard.database", - "db_api", -) - -Roles: "IRoles" = get_module("standard.roles", "Roles") - - def register_command(command, description, role="USER"): if role not in commands: commands[role] = dict() @@ -31,53 +14,6 @@ def register_command(command, description, role="USER"): } -class OuterMiddleware(BaseMiddleware): - async def __call__( - self, - handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], - event: TelegramObject, - data: Dict[str, Any], - ): - - # if not isinstance(event, Message): - # return await handler(event, data) - # - # user = db_api.get_user(event.from_user.id) - # - # if user is None: - # return - # - # roles = Roles() - # role_name = await roles.get_role_name(role_id=user.user_role) - # - # if role_name not in commands: - # return await handler(event, data) - - # bot_commands = [] - - # for role_command in commands[role_name]: - # bot_commands.append( - # BotCommand( - # command=role_command, - # description=commands[role_name][role_command]["description"], - # ) - # ) - - # await event.bot.set_my_commands( - # bot_commands, - # BotCommandScopeChatMember( - # chat_id=event.chat.id, - # user_id=event.from_user.id, - # ), - # ) - - return await handler(event, data) - - -async def module_init(): - register_outer_message_middleware(OuterMiddleware()) - - async def set_user_commands(): bot_commands = [] if "USER" in commands: @@ -90,6 +26,8 @@ async def set_user_commands(): ) ) + log(bot_commands) + await set_my_commands( bot_commands, ) diff --git a/src/ocab_modules/ocab_modules/standard/config/config_manager.py b/src/ocab_modules/ocab_modules/standard/config/config_manager.py index b6b1a1c..adbcfe3 100644 --- a/src/ocab_modules/ocab_modules/standard/config/config_manager.py +++ b/src/ocab_modules/ocab_modules/standard/config/config_manager.py @@ -84,7 +84,8 @@ class ConfigManager: key: str, value_type: str, options: List[Any] = None, - default_value=None, + multiple: bool = False, + default_value = None, editable: bool = True, shared: bool = False, required: bool = False, @@ -101,6 +102,7 @@ class ConfigManager: self._metadata[key] = { "type": value_type, + "multiple": multiple, "options": options, "default_value": default_value, "visible": visible, diff --git a/src/ocab_modules/ocab_modules/standard/filters/__init__.py b/src/ocab_modules/ocab_modules/standard/filters/__init__.py index 7b423c4..ed9efdd 100644 --- a/src/ocab_modules/ocab_modules/standard/filters/__init__.py +++ b/src/ocab_modules/ocab_modules/standard/filters/__init__.py @@ -1,6 +1,7 @@ from .filters import ( ChatModerOrAdminFilter, ChatNotInApproveFilter, + ChatIDFilter, chat_not_in_approve, module_init, ) diff --git a/src/ocab_modules/ocab_modules/standard/filters/filters.py b/src/ocab_modules/ocab_modules/standard/filters/filters.py index 6e71916..102bf42 100644 --- a/src/ocab_modules/ocab_modules/standard/filters/filters.py +++ b/src/ocab_modules/ocab_modules/standard/filters/filters.py @@ -1,4 +1,5 @@ from typing import TYPE_CHECKING +from typing_extensions import deprecated from aiogram import Bot from aiogram.filters import BaseFilter @@ -8,35 +9,63 @@ from ocab_core.modules_system.public_api import get_module, log if TYPE_CHECKING: from ocab_modules.standard.config import IConfig + from ocab_modules.standard.roles import Roles as IRoles + config: "IConfig" = get_module("standard.config", "config") -Roles = get_module("standard.roles", "Roles") +try: + Roles: "type[IRoles]" = get_module("standard.roles", "Roles") + ROLES_MODULE_LOADED = True +except Exception: + ROLES_MODULE_LOADED = False + pass def module_init(): - config.register("filters::approved_chat_id", "string", shared=True) + config.register("filters::approved_chat_id", "int", multiple=True, shared=True, default_value=[]) config.register("filters::default_chat_tag", "string", shared=True) def get_approved_chat_id() -> list: - # Возваращем сплитованный список id чатов в формате int - return [ - int(chat_id) for chat_id in config.get("filters::approved_chat_id").split(" | ") - ] + return config.get("filters::approved_chat_id") +@deprecated("Use ChatIDFilter or own implementation") def chat_not_in_approve(message: Message) -> bool: chat_id = message.chat.id if chat_id in get_approved_chat_id(): - log(f"Chat in approve list: {chat_id}") + # log(f"Chat in approve list: {chat_id}") return False else: - log(f"Chat not in approve list: {chat_id}") + # log(f"Chat not in approve list: {chat_id}") return True +class ChatIDFilter(BaseFilter): + def __init__(self, blacklist = False, approved_chats = None) -> None: + self.blacklist = blacklist + self.approved_chats = approved_chats + super().__init__() + + async def __call__(self, message: Message, bot: Bot) -> bool: + chat_id = message.chat.id + + approved_chats = self.approved_chats or get_approved_chat_id() + + print(approved_chats) + + res = chat_id in approved_chats + + return res ^ (self.blacklist) + +class ChatNotInApproveFilter(ChatIDFilter): + def __init__(self) -> None: + super().__init__(allow = False) class ChatModerOrAdminFilter(BaseFilter): async def __call__(self, message: Message, bot: Bot) -> bool: + if not ROLES_MODULE_LOADED: + raise Exception("Roles module not loaded") + user_id = message.from_user.id roles = Roles() admins = await bot.get_chat_administrators(message.chat.id) @@ -44,9 +73,4 @@ class ChatModerOrAdminFilter(BaseFilter): await roles.check_admin_permission(user_id) or await roles.check_moderator_permission(user_id) or any(user_id == admin.user.id for admin in admins) - ) - - -class ChatNotInApproveFilter(BaseFilter): - async def __call__(self, message: Message, bot: Bot) -> bool: - return chat_not_in_approve(message) + ) \ No newline at end of file diff --git a/src/ocab_modules/ocab_modules/standard/filters/info.json b/src/ocab_modules/ocab_modules/standard/filters/info.json index 205b829..9554bea 100644 --- a/src/ocab_modules/ocab_modules/standard/filters/info.json +++ b/src/ocab_modules/ocab_modules/standard/filters/info.json @@ -4,11 +4,13 @@ "description": "Модуль с фильтрами", "author": "OCAB Team", "version": "1.0.0", - "privileged": false, + "privileged": true, "dependencies": { "required": { - "standard.roles": "^1.0.0", "standard.config": "^1.0.0" + }, + "optional": { + "standard.roles": "^1.0.0" } } } diff --git a/src/ocab_modules/ocab_modules/standard/report/README.md b/src/ocab_modules/ocab_modules/standard/report/README.md new file mode 100644 index 0000000..164d17d --- /dev/null +++ b/src/ocab_modules/ocab_modules/standard/report/README.md @@ -0,0 +1,18 @@ +# Модуль Report + +Модуль `report` позволяет пользователям сообщать о спам-сообщениях в чате. + +## Команды + +- `/report` - пожаловаться на сообщение как на спам. + +## Использование + +Чтобы сообщить о сообщении как о спаме, отправьте команду `/report`, ответив на сообщение, которое вы хотите отметить. Модуль уведомит администраторов, которые имеют права модерации. + +### Пример использования + +1. Найдите сообщение, которое вы хотите отметить как спам. +2. Ответьте на это сообщение командой `/report`. + +Примечание: Команда `/report` должна быть отправлена в ответ на сообщение, которое вы хотите отметить. diff --git a/src/ocab_modules/ocab_modules/standard/report/__init__.py b/src/ocab_modules/ocab_modules/standard/report/__init__.py new file mode 100644 index 0000000..729f10a --- /dev/null +++ b/src/ocab_modules/ocab_modules/standard/report/__init__.py @@ -0,0 +1 @@ +from .main import module_init \ No newline at end of file diff --git a/src/ocab_modules/ocab_modules/standard/report/info.json b/src/ocab_modules/ocab_modules/standard/report/info.json new file mode 100644 index 0000000..ac5e2f3 --- /dev/null +++ b/src/ocab_modules/ocab_modules/standard/report/info.json @@ -0,0 +1,14 @@ +{ + "id": "standard.report", + "name": "Report", + "description": "Модуль для быстрой жалобы на спам", + "author": "OCAB Team", + "version": "1.0.0", + "privileged": false, + "dependencies": { + "optional": { + "standard.command_helper": "^1.0.0", + "standard.filters": "^1.0.0" + } + } +} diff --git a/src/ocab_modules/ocab_modules/standard/report/main.py b/src/ocab_modules/ocab_modules/standard/report/main.py new file mode 100644 index 0000000..aa744c8 --- /dev/null +++ b/src/ocab_modules/ocab_modules/standard/report/main.py @@ -0,0 +1,69 @@ +from typing import TYPE_CHECKING +from aiogram import Router +from aiogram.filters import Command +from aiogram.types import Message, ChatMemberOwner, ChatMemberAdministrator + +from ocab_core.modules_system.public_api import get_module, register_router, log + +if TYPE_CHECKING: + from ocab_modules.standard.filters import ChatIDFilter as IChatIDFilter + +try: + ChatIDFilter: "type[IChatIDFilter]" = get_module("standard.filters", "ChatIDFilter") + FILTERS_MODULE_LOADED = True +except Exception as e: + FILTERS_MODULE_LOADED = False + pass + +try: + register_command = get_module("standard.command_helper", "register_command") + COMMAND_HELPER_MODULE_LOADED = True +except Exception as e: + COMMAND_HELPER_MODULE_LOADED = False + pass + +def can_moderate(admin: ChatMemberOwner | ChatMemberAdministrator) -> bool: + if isinstance(admin, ChatMemberOwner): + return True + + return ( + admin.user.is_bot == False and + ( + admin.can_delete_messages and + admin.can_restrict_members + ) + ) + +async def report(message: Message): + try: + if message.reply_to_message is None: + await message.reply("Пожалуйста, используйте команду /report в ответ на сообщение, которое вы хотите отметить как спам.") + return + + admins = await message.chat.get_administrators() + + admin_usernames = [ + admin.user.mention_html() + for admin in admins + if can_moderate(admin) + ] + if admin_usernames: + ping_message = "⚠️ Внимание, жалоба на спам! " + ", ".join(admin_usernames) + await message.reply_to_message.reply(ping_message, parse_mode="HTML") + except Exception as e: + log(e) + + +async def module_init(): + router = Router() + + if FILTERS_MODULE_LOADED: + router.message.register(report, ChatIDFilter(), Command("report")) + else: + router.message.register(report, Command("report")) + + register_router(router) + + if COMMAND_HELPER_MODULE_LOADED: + register_command = get_module("standard.command_helper", "register_command") + register_command("report", "Пожаловаться на спам") \ No newline at end of file