diff options
| -rw-r--r-- | hydroroll/__init__.py | 223 | ||||
| -rw-r--r-- | hydroroll/bot.py | 50 | ||||
| -rw-r--r-- | hydroroll/config.py | 75 | ||||
| -rw-r--r-- | hydroroll/core/__init__.py | 70 | ||||
| -rw-r--r-- | hydroroll/exceptions.py | 0 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_base/config.py | 30 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_bot/__init__.py | 30 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_bot/config.py | 11 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_echo/__init__.py | 17 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_echo/config.py | 11 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_luck/__init__.py | 21 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_luck/config.py | 15 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_send/__init__.py | 28 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_send/config.py | 15 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_system/__init__.py | 75 | ||||
| -rw-r--r-- | hydroroll/plugins/plugin_system/config.py | 11 | ||||
| -rw-r--r-- | hydroroll/psi/.gitkeep | 0 | ||||
| -rw-r--r-- | hydroroll/typing.py | 34 | ||||
| -rw-r--r-- | hydroroll/utils.py (renamed from hydroroll/plugins/plugin_base/__init__.py) | 14 |
19 files changed, 393 insertions, 337 deletions
diff --git a/hydroroll/__init__.py b/hydroroll/__init__.py index c72c991..b064bca 100644 --- a/hydroroll/__init__.py +++ b/hydroroll/__init__.py @@ -1,4 +1,221 @@ -name = "hydroroll" +from collections import defaultdict +from itertools import chain +from pathlib import Path +from typing import Dict, List, Optional, Type, Union +from iamai import ConfigModel, Plugin +from iamai.log import logger, error_or_exception +from iamai.utils import ModulePathFinder, get_classes_from_module_name, get_classes_from_dir, get_classes_from_module +from .config import GlobalConfig +from iamai.exceptions import LoadModuleError +from .utils import BasePlugin, RegexPluginBase, CommandPluginBase +import os +# from .core import Rule, RuleLoadType +from HydroRollCore import Rule, RuleLoadType -from hydroroll.bot import Bot -from hydroroll.config import GlobalConfig
\ No newline at end of file +class HydroRoll(Plugin): + + class Config(ConfigModel): + __config_name__ = "HydroRoll" + + priority = 0 + rules_priority_dict: Dict[int, List[Type[Rule]]] = defaultdict(list) + _module_path_finder = ModulePathFinder() + + + def _load_rule_class( + self, + rule_class: Type[Rule], + rule_load_type: RuleLoadType, + rule_file_path: Optional[str], + ): + logger.info(f'Loading rule from class "{rule_class!r}"') + """加载规则类。""" + priority = getattr(rule_class, "priority", None) + if type(priority) is int and priority >= 0: + for _rule in self.rules: + if _rule.__name__ == rule_class.__name__: + logger.warning( + f'Already have a same name rule "{_rule.__name__}"' + ) + rule_class.__rule_load_type__ = rule_load_type + rule_class.__rule_file_path__ = rule_file_path + self.rules_priority_dict[priority].append(rule_class) + logger.success( + f'Succeeded to load rule "{rule_class.__name__}" ' + f'from class "{rule_class!r}"' + ) + else: + error_or_exception( + f'Load rule from class "{rule_class!r}" failed:', + LoadModuleError( + f'Rule priority incorrect in the class "{rule_class!r}"' + ), + self.bot.config.bot.log.verbose_exception, + ) + + def _load_rules_from_module_name( + self, module_name: str, rule_load_type: RuleLoadType + ): + logger.info(f'Loading rules from module "{module_name}"') + """从模块名称中规则包模块。""" + try: + rule_classes = get_classes_from_module_name(module_name, Rule) + except ImportError as e: + error_or_exception( + f'Import module "{module_name}" failed:', + e, + self.bot.config.bot.log.verbose_exception, + ) + else: + for rule_class, module in rule_classes: + self._load_rule_class( + rule_class, + rule_load_type, + module.__file__, + ) + + def _load_rules( + self, + *rules: Union[Type[Plugin], str, Path], + rule_load_type: Optional[RuleLoadType] = None, + ): + """加载规则包。 + + Args: + *rules: 规则类、规则包模块名称或者规则包模块文件路径。类型可以是 `Type[Rule]`, `str` 或 `pathlib.Path`。 + 如果为 `Type[Rule]` 类型时,将作为规则类进行加载。 + 如果为 `str` 类型时,将作为规则包模块名称进行加载,格式和 Python `import` 语句相同。 + 例如:`path.of.rule`。 + 如果为 `pathlib.Path` 类型时,将作为规则包模块文件路径进行加载。 + 例如:`pathlib.Path("path/of/rule")`。 + rule_load_type: 规则加载类型,如果为 None 则自动判断,否则使用指定的类型。 + """ + logger.info("Loading rules...") + for rule_ in rules: + if isinstance(rule_, type): + if issubclass(rule_, Rule): + self._load_rule_class( + rule_, rule_load_type or RuleLoadType.CLASS, None + ) + else: + logger.error( + f'The rule class "{rule_!r}" must be a subclass of Rule' + ) + elif isinstance(rule_, str): + logger.warning(f'Loading rules from module "{rule_}"') + self._load_rules_from_module_name( + rule_, rule_load_type or RuleLoadType.NAME + ) + elif isinstance(rule_, Path): + logger.warning(f'Loading rules from path "{rule_}"') + if rule_.is_file(): + if rule_.suffix != ".py": + logger.error(f'The path "{rule_}" must endswith ".py"') + return + + rule_module_name = None + for path in self._module_path_finder.path: + try: + if rule_.stem == "__init__": + if rule_.resolve().parent.parent.samefile(Path(path)): + rule_module_name = rule_.resolve().parent.name + break + elif rule_.resolve().parent.samefile(Path(path)): + rule_module_name = rule_.stem + break + except OSError: + continue + if rule_module_name is None: + rel_path = rule_.resolve().relative_to(Path(".").resolve()) + if rel_path.stem == "__init__": + rule_module_name = ".".join(rel_path.parts[:-1]) + else: + rule_module_name = ".".join( + rel_path.parts[:-1] + (rel_path.stem,) + ) + + self._load_rules_from_module_name( + rule_module_name, rule_load_type or RuleLoadType.FILE + ) + else: + logger.error(f'The rule path "{rule_}" must be a file') + else: + logger.error(f"Type error: {rule_} can not be loaded as plugin") + + def load_rules(self, *rules: Union[Type[Rule], str, Path]): + """加载规则。 + + Args: + *rules: 规则类、规则包x模块名称或者规则包模块文件路径。类型可以是 `Type[Rule]`, `str` 或 `pathlib.Path`。 + 如果为 `Type[Rule]` 类型时,将作为规则类进行加载。 + 如果为 `str` 类型时,将作为规则包模块名称进行加载,格式和 Python `import` 语句相同。 + 例如:`path.of.rule`。 + 如果为 `pathlib.Path` 类型时,将作为规则包模块文件路径进行加载。 + 例如:`pathlib.Path("path/of/rule")`。 + """ + # self._extend_rules.extend(rules) + return self._load_rules(*rules) + + def _load_rules_from_dirs(self, *dirs: Path): + """从目录中加载规则包,以 `_` 开头的模块中的规则不会被导入。路径可以是相对路径或绝对路径。 + + Args: + *dirs: 储存包含规则的模块的模块路径。 + 例如:`pathlib.Path("path/of/rules/")` 。 + """ + logger.info("Loading rules from dirs...") + dirs = list(map(lambda x: str(x.resolve()), dirs)) # type: ignore maybe remove? + logger.warning(f'Loading rules from dirs "{", ".join(map(str, dirs))}"') + self._module_path_finder.path.extend(dirs) # type: ignore + # type: ignore + for rule_class, module in get_classes_from_dir(dirs, Rule): # type: ignore + self._load_rule_class(rule_class, RuleLoadType.DIR, module.__file__) + + def load_rules_from_dirs(self, *dirs: Path): + """从目录中加载规则,以 `_` 开头的模块中的规则不会被导入。路径可以是相对路径或绝对路径。 + + Args: + *dirs: 储存包含rule的模块的模块路径。 + 例如:`pathlib.Path("path/of/rules/")` 。 + """ + # self._extend_rule_dirs.extend(dirs) + self._load_rules_from_dirs(*dirs) + + @property + def rules(self) -> List[Type[Plugin]]: + """当前已经加载的规则包的列表。""" + return list(chain(*self.rules_priority_dict.values())) + + def __post_init__(self): + if not self.bot.global_state.get('init', False): + self.bot.global_state = dict() + self.bot.global_state['init'] = True + + self._load_rules_from_dirs(Path(os.path.join("\\".join(os.path.dirname(__file__).split('\\')[:-2]),"rules"))) #*self.config.rule['rule_dirs']) + # self._load_rules(*self.config.rule.rules) + + ... + + async def handle(self) -> None: + """ + @TODO: HydroRollCore should be able to handle all signals and tokens from Psi. + @BODY: HydroRollCore actives the rule-packages. + """ + + if self.event.message.get_plain_text() == '.core': + await self.event.reply("HydroRollCore is running.") + elif self.event.message.startswith('.show'): + try: + await self.event.reply(eval(self.event.message.get_plain_text()[6:])) + except Exception as e: + await self.event.reply(f"{e!r}") + + async def rule(self) -> bool: + """ + @TODO: Psi should be able to handle all message first. + @BODY: lexer module will return a list of tokens, parser module will parse the tokens into a tree, and executor module will execute the tokens with a stack with a bool return value. + """ + + if self.event.type != "message": + return False + return self.event.message.get_plain_text().startswith(".")
\ No newline at end of file diff --git a/hydroroll/bot.py b/hydroroll/bot.py deleted file mode 100644 index cbfc07f..0000000 --- a/hydroroll/bot.py +++ /dev/null @@ -1,50 +0,0 @@ -from iamai import Bot as _Bot -from typing import Optional, Dict, List, Type, Any, Union -from pathlib import Path -import os -from hydroroll.config import GlobalConfig - -current_dir = Path.cwd() -script_file = current_dir.resolve() / __file__ -script_dir = script_file.parent - -__all__ = ["Bot"] - -class Bot: - def __init__( - self, - *, - config_file: Optional[str] = "config.toml", - config_dict: Optional[Dict] = None, - hot_reload: bool = False, - ) -> None: - self.bot = _Bot(hot_reload=hot_reload, - config_file=config_file, - config_dict=config_dict - ) - self.bot.load_plugins_from_dirs(Path(f"{script_dir}/plugins")) - self.create_folder_structure(GlobalConfig._folder_dict) - - def run(self) -> None: - self.bot.run() - - def restart(self) -> None: - self.bot.restart() - - def create_folders(self): - folder_path = os.path.dirname(os.path.abspath('__file__')) # 获取main.py所在文件夹路径 - if not os.path.isdir(os.path.join(folder_path, 'user')): - os.mkdir(os.path.join(folder_path, 'user')) - if not os.path.isdir(os.path.join(folder_path, 'data')): - os.mkdir(os.path.join(folder_path, 'data')) - if not os.path.isdir(os.path.join(folder_path, 'models')): - os.mkdir(os.path.join(folder_path, 'models')) - if not os.path.isdir(os.path.join(folder_path, 'web')): - os.mkdir(os.path.join(folder_path, 'web')) - if not os.path.isdir(os.path.join(folder_path, 'config')): - os.mkdir(os.path.join(folder_path, 'config')) - if not os.path.isdir(os.path.join(folder_path, 'logs')): - os.mkdir(os.path.join(folder_path, 'logs')) - if not os.path.isdir(os.path.join(folder_path, 'rules')): - os.mkdir(os.path.join(folder_path, 'rules')) - diff --git a/hydroroll/config.py b/hydroroll/config.py index 4258606..7cd7520 100644 --- a/hydroroll/config.py +++ b/hydroroll/config.py @@ -2,34 +2,79 @@ import argparse import sys import platform from importlib.metadata import version -from iamai import Plugin import os +from typing import Set, Optional +from iamai import ConfigModel # 创建全局 ArgumentParser 对象 -global_parser = argparse.ArgumentParser(description='hydroroll[水系] 全局命令参数') +global_parser = argparse.ArgumentParser(description="HydroRoll[水系] 全局命令参数") + +class BasePluginConfig(ConfigModel): + __config_name__ = "" + handle_all_message: bool = True + """是否处理所有类型的消息,此配置为 True 时会覆盖 handle_friend_message 和 handle_group_message。""" + handle_friend_message: bool = True + """是否处理好友消息。""" + handle_group_message: bool = True + """是否处理群消息。""" + accept_group: Optional[Set[int]] = None + """处理消息的群号,仅当 handle_group_message 为 True 时生效,留空表示处理所有群。""" + message_str: str = "*{user_name} {message}" + """最终发送消息的格式。""" + + +class RegexPluginConfig(BasePluginConfig): + pass + + +class CommandPluginConfig(RegexPluginConfig): + command_prefix: Set[str] = {":", "你妈", "👅", "约瑟夫妥斯妥耶夫斯基戴安那只鸡🐔"} + """命令前缀。""" + command: Set[str] = {} + """命令文本。""" + ignore_case: bool = True + """忽略大小写。""" + # 定义全局配置类 -class GlobalConfig: - _name = "hydroroll" +class GlobalConfig(CommandPluginConfig): + _name = "HydroRoll[水系]" _version = "0.1.0" _svn = "2" _author = "简律纯" - _iamai_version = version('iamai') + _iamai_version = version("iamai") _python_ver = sys.version - _python_ver_raw= '.'.join(map(str, platform.python_version_tuple()[:3])) - _current_path = os.path.dirname(os.path.abspath('__file__')) - _folders = {'config':{},'data':{},'logs':{},'models':{},'rules':{'rules_default'},'users':{},'web':{'frontend':{'static','js','css','public'},'backend':{'app','template'}}} - + _python_ver_raw = ".".join(map(str, platform.python_version_tuple()[:3])) + current_path = os.path.dirname(os.path.abspath("__file__")) + # 定义系统组件 class HydroSystem: def __init__(self): - self.parser = argparse.ArgumentParser(description='hydroroll[水系].system 系统命令参数') + self.parser = argparse.ArgumentParser( + description="HydroRoll[水系].system 系统命令参数" + ) self.subparsers = self.parser.add_subparsers() - self.status_parser = self.subparsers.add_parser('status', aliases=['s'], help='系统状态') - self.reload_parser = self.subparsers.add_parser('reload', aliases=['rld'], help='重新加载系统') - self.restart_parser = self.subparsers.add_parser('restart', aliases=['rst'], help='重启系统') - self.help = '\n'.join(self.parser.format_help().replace('\r\n', '\n').replace('\r', '').split('\n')[2:-3]) + self.status_parser = self.subparsers.add_parser( + "status", aliases=["stat", "state"], help="系统状态" + ) + self.reload_parser = self.subparsers.add_parser( + "reload", aliases=["rld"], help="重新加载系统" + ) + self.restart_parser = self.subparsers.add_parser( + "restart", aliases=["rst"], help="重启系统" + ) + self.collect_parser = self.subparsers.add_parser( + "collect", aliases=["gc"], help="释放 python 内存" + ) + self.help = "\n".join( + self.parser.format_help() + .replace("\r\n", "\n") + .replace("\r", "") + .split("\n")[2:-3] + ) + class HydroBot: def __init__(self) -> None: self.parser = argparse.ArgumentParser(description="Bot命令") -
\ No newline at end of file + + diff --git a/hydroroll/core/__init__.py b/hydroroll/core/__init__.py new file mode 100644 index 0000000..ed02489 --- /dev/null +++ b/hydroroll/core/__init__.py @@ -0,0 +1,70 @@ +from abc import ABC, abstractmethod +import os, os.path +from enum import Enum +from iamai.config import ConfigModel +from iamai.utils import is_config_class +from typing import Generic, NoReturn, Optional, Type, TYPE_CHECKING +from ...HydroRoll.typing import T_Event, T_Config + +if TYPE_CHECKING: + from iamai.bot import Bot + +class RuleLoadType(Enum): + """插件加载类型。""" + + DIR = "dir" + NAME = "name" + FILE = "file" + CLASS = "class" + + +class Rule(ABC, Generic[T_Event, T_Config]): + """所有 iamai 插件的基类。 + + Attributes: + event: 当前正在被此插件处理的事件。 + priority: 插件的优先级,数字越小表示优先级越高,默认为 0。 + block: 插件执行结束后是否阻止事件的传播。True 表示阻止。 + __rule_load_type__: 插件加载类型,由 iamai 自动设置,反映了此插件是如何被加载的。 + __rule_file_path__: 当插件加载类型为 `RuleLoadType.CLASS` 时为 `None`, + 否则为定义插件在的 Python 模块的位置。 + """ + + event: T_Event + priority: int = 0 + Config: Type[ConfigModel] + + __rule_load_type__: RuleLoadType + __rule_file_path__: Optional[str] + + def __init__(self, event: T_Event): + self.event = event + + if not hasattr(self, "priority"): + self.priority = 0 + + self.get = self.bot.get + + self.__post_init__() + + def __post_init__(self): + """用于初始化后处理,被 `__init__()` 方法调用。""" + pass + + @property + def name(self) -> str: + """规则类名称。""" + return self.__class__.__name__ + + @property + def bot(self) -> "Bot": + """机器人对象。""" + return self.event.adapter.bot + + @property + def config(self) -> Optional[T_Config]: + """规则包配置。""" + config_class: ConfigModel = getattr(self, "Rule", None) + if is_config_class(config_class): + return getattr(self.config.rule, config_class.__config_name__, None) + return None
\ No newline at end of file diff --git a/hydroroll/exceptions.py b/hydroroll/exceptions.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/hydroroll/exceptions.py diff --git a/hydroroll/plugins/plugin_base/config.py b/hydroroll/plugins/plugin_base/config.py deleted file mode 100644 index 311874c..0000000 --- a/hydroroll/plugins/plugin_base/config.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Set, Optional - -from iamai import ConfigModel - - -class BasePluginConfig(ConfigModel): - __config_name__ = "" - handle_all_message: bool = True - """是否处理所有类型的消息,此配置为 True 时会覆盖 handle_friend_message 和 handle_group_message。""" - handle_friend_message: bool = True - """是否处理好友消息。""" - handle_group_message: bool = True - """是否处理群消息。""" - accept_group: Optional[Set[int]] = None - """处理消息的群号,仅当 handle_group_message 为 True 时生效,留空表示处理所有群。""" - message_str: str = "*{user_name} {message}" - """最终发送消息的格式。""" - - -class RegexPluginConfig(BasePluginConfig): - pass - - -class CommandPluginConfig(RegexPluginConfig): - command_prefix: Set[str] = {".", "。","!",":"} - """命令前缀。""" - command: Set[str] = {} - """命令文本。""" - ignore_case: bool = True - """忽略大小写。""" diff --git a/hydroroll/plugins/plugin_bot/__init__.py b/hydroroll/plugins/plugin_bot/__init__.py deleted file mode 100644 index 89ba17b..0000000 --- a/hydroroll/plugins/plugin_bot/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -import re -from importlib.metadata import version -from plugins.plugin_base import CommandPluginBase -from hydroroll.config import GlobalConfig - -from .config import Config - - -class HydroBot(CommandPluginBase[None, Config]): - Config = Config - CurrentConfig = GlobalConfig - priority = 0 - - def __post_init__(self): - self.re_pattern = re.compile(r"(?P<bot_info_str>.*)", flags=re.I) - - def bot_info(self): - info_str = f'{self.CurrentConfig._name} '\ - f'{self.CurrentConfig._version}({self.CurrentConfig._svn}) '\ - f'by {self.CurrentConfig._author} '\ - f'on Python {self.CurrentConfig._python_ver_raw} '\ - f'with {" & ".join([adapter + "("+version("iamai-adapter-"+adapter) +")" for adapter in dict(self.bot.config.adapter)])} '\ - f'for iamai({self.CurrentConfig._iamai_version})' - - return info_str - - async def handle(self) -> None: - await self.event.reply( - self.format_str(self.config.message_str, self.bot_info()) - ) diff --git a/hydroroll/plugins/plugin_bot/config.py b/hydroroll/plugins/plugin_bot/config.py deleted file mode 100644 index 640f23a..0000000 --- a/hydroroll/plugins/plugin_bot/config.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Set - -from plugins.plugin_base import CommandPluginConfig - - -class Config(CommandPluginConfig): - __config_name__ = "plugin_bot_info" - command: Set[str] = {"bot"} - """命令文本。""" - message_str: str = "{message}" - """最终发送消息的格式。""" diff --git a/hydroroll/plugins/plugin_echo/__init__.py b/hydroroll/plugins/plugin_echo/__init__.py deleted file mode 100644 index faa47df..0000000 --- a/hydroroll/plugins/plugin_echo/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -import re - -from plugins.plugin_base import CommandPluginBase - -from .config import Config - - -class Echo(CommandPluginBase[None, Config]): - Config = Config - - def __post_init__(self): - self.re_pattern = re.compile(r"(?P<echo_str>.*)", flags=re.I) - - async def handle(self) -> None: - await self.event.reply( - self.format_str(self.config.message_str, self.msg_match.group("echo_str")) - ) diff --git a/hydroroll/plugins/plugin_echo/config.py b/hydroroll/plugins/plugin_echo/config.py deleted file mode 100644 index c047134..0000000 --- a/hydroroll/plugins/plugin_echo/config.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Set - -from plugins.plugin_base import CommandPluginConfig - - -class Config(CommandPluginConfig): - __config_name__ = "plugin_echo" - command: Set[str] = {"echo"} - """命令文本。""" - message_str: str = "*{user_name} {message}" - """最终发送消息的格式。""" diff --git a/hydroroll/plugins/plugin_luck/__init__.py b/hydroroll/plugins/plugin_luck/__init__.py deleted file mode 100644 index 7967a0b..0000000 --- a/hydroroll/plugins/plugin_luck/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -import re -import time -import random - -from plugins.plugin_base import CommandPluginBase - -from .config import Config - - -class Luck(CommandPluginBase[None, Config]): - Config = Config - - def __post_init__(self): - self.re_pattern = re.compile(r".*", flags=re.I) - - async def handle(self) -> None: - random.seed( - time.strftime("%Y%j", time.localtime()) + self.format_str("{user_id}") - ) - lucy = random.randint(self.config.min_int, self.config.max_int) - await self.event.reply(self.format_str(self.config.message_str, str(lucy))) diff --git a/hydroroll/plugins/plugin_luck/config.py b/hydroroll/plugins/plugin_luck/config.py deleted file mode 100644 index 6190531..0000000 --- a/hydroroll/plugins/plugin_luck/config.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Set - -from plugins.plugin_base import CommandPluginConfig - - -class Config(CommandPluginConfig): - __config_name__ = "plugin_luck" - command: Set[str] = {"luck"} - """命令文本。""" - min_int: int = 0 - """最小随机整数。""" - max_int: int = 100 - """最大随机整数。""" - message_str: str = "{user_name}今天的运气是: {message}" - """最终发送消息的格式。""" diff --git a/hydroroll/plugins/plugin_send/__init__.py b/hydroroll/plugins/plugin_send/__init__.py deleted file mode 100644 index 2468189..0000000 --- a/hydroroll/plugins/plugin_send/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -import re - -from plugins.plugin_base import CommandPluginBase - -from .config import Config - - -class Send(CommandPluginBase[None, Config]): - Config = Config - - def __post_init__(self): - self.re_pattern = re.compile(r"\s*(?P<message>.*)", flags=re.I) - - async def handle(self) -> None: - try: - await self.event.adapter.send( - self.msg_match.group("message"), - "private", - self.config.send_user_id, - ) - except Exception as e: - if self.config.send_filed_msg is not None: - await self.event.reply( - self.format_str(self.config.send_filed_msg, repr(e)) - ) - else: - if self.config.send_success_msg is not None: - await self.event.reply(self.format_str(self.config.send_success_msg)) diff --git a/hydroroll/plugins/plugin_send/config.py b/hydroroll/plugins/plugin_send/config.py deleted file mode 100644 index 5b7fe7b..0000000 --- a/hydroroll/plugins/plugin_send/config.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Set, Optional - -from plugins.plugin_base import CommandPluginConfig - - -class Config(CommandPluginConfig): - __config_name__ = "plugin_send" - command: Set[str] = {"send"} - """命令文本。""" - send_user_id: int = 2753364619 - """发送消息的对象的 QQ 号码。""" - send_success_msg: Optional[str] = "已将消息送出√" - """发送成功时回复的消息,设置为 None 表示不发送任何消息。""" - send_filed_msg: Optional[str] = "发送失败:{message}" - """发送失败时回复的消息。""" diff --git a/hydroroll/plugins/plugin_system/__init__.py b/hydroroll/plugins/plugin_system/__init__.py deleted file mode 100644 index ea797d3..0000000 --- a/hydroroll/plugins/plugin_system/__init__.py +++ /dev/null @@ -1,75 +0,0 @@ -import re -from plugins.plugin_base import CommandPluginBase -from .config import Config -import psutil -import time -from hydroroll.config import GlobalConfig -from iamai.adapter.cqhttp.message import CQHTTPMessageSegment - -class System(CommandPluginBase[None, Config]): - priority: int = 0 - block: bool = True - Config = Config - CurrentConfig = GlobalConfig - - def __post_init__(self): - self.re_pattern = re.compile(r"(?P<system_info_str>.*)", flags=re.I) - - def eventReply(self, message: str): - return self.event.reply( - self.format_str(self.config.message_str, message) - ) - - def get_system_status(self) -> str: - cpu_usage = psutil.cpu_percent() - memory_usage = psutil.virtual_memory().percent - disk_usage = psutil.disk_usage('/').percent - - current_time = time.time() - start_time = psutil.Process().create_time() - - uptime_seconds = int(current_time - start_time) - uptime_str = time.strftime("%H:%M:%S", time.gmtime(uptime_seconds)) - - info_str = f"{self.CurrentConfig._name} Ver.{self.CurrentConfig._version}" - info_str += f"({self.CurrentConfig._svn}) built in Python {self.CurrentConfig._python_ver}\n" - info_str += f"本地时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}\n" - info_str += f"已启动时间:{uptime_str}\n" - info_str += f"CPU使用率:{cpu_usage}%\n" - info_str += f"内存占用率:{memory_usage}%\n" - info_str += f"磁盘使用率:{disk_usage}%" - - return info_str - - async def handle(self) -> None: - system = self.CurrentConfig.HydroSystem() - try: - sub_command = self.event.get_plain_text().split()[1] - except IndexError: - sub_command = "" - - if sub_command in ["status", "s"]: - await self.event.reply( - self.format_str(self.config.message_str, - self.get_system_status()) - ) - elif sub_command in ["restart", "rst"]: - await self.event.reply( - self.format_str(self.config.message_str, "正在重启系统...") - ) - self.bot.restart() - - elif sub_command in ["reload", "rld"]: - await self.event.reply( - self.format_str(self.config.message_str, "正在重载...") - ) - self.bot.reload_plugins() - await self.event.reply( - self.format_str(self.config.message_str, - f"已加载{len(self.bot.plugins)}枚插件") - ) - else: - await self.event.reply( - CQHTTPMessageSegment.reply(self.event.message_id) + - self.format_str(self.config.message_str, system.help) - ) diff --git a/hydroroll/plugins/plugin_system/config.py b/hydroroll/plugins/plugin_system/config.py deleted file mode 100644 index aa5f51b..0000000 --- a/hydroroll/plugins/plugin_system/config.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Set - -from plugins.plugin_base import CommandPluginConfig - - -class Config(CommandPluginConfig): - __config_name__ = "plugin_system_info" - command: Set[str] = {"system"} - """命令文本。""" - message_str: str = "{message}" - """最终发送消息的格式。""" diff --git a/hydroroll/psi/.gitkeep b/hydroroll/psi/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/hydroroll/psi/.gitkeep diff --git a/hydroroll/typing.py b/hydroroll/typing.py new file mode 100644 index 0000000..876fa92 --- /dev/null +++ b/hydroroll/typing.py @@ -0,0 +1,34 @@ +"""HydroRoll 类型提示支持。 + +此模块定义了部分 HydroRoll 使用的类型。 +""" + +from typing import TYPE_CHECKING, TypeVar, Callable, NoReturn, Awaitable + +from iamai.message import T_MS, T_Message, T_MessageSegment + +if TYPE_CHECKING: + from iamai.bot import Bot # noqa + from iamai.event import Event # noqa + from iamai.plugin import Plugin # noqa + from iamai.config import ConfigModel # noqa + +__all__ = [ + "T_State", + "T_Event", + "T_Plugin", + "T_Config", + "T_Message", + "T_MessageSegment", + "T_MS", + "T_BotHook", + "T_EventHook", +] + +T_State = TypeVar("T_State") +T_Event = TypeVar("T_Event", bound="Event") +T_Plugin = TypeVar("T_Plugin", bound="Plugin") +T_Config = TypeVar("T_Config", bound="ConfigModel") + +T_BotHook = Callable[["Bot"], Awaitable[NoReturn]] +T_EventHook = Callable[[T_Event], Awaitable[NoReturn]]
\ No newline at end of file diff --git a/hydroroll/plugins/plugin_base/__init__.py b/hydroroll/utils.py index a050ae1..b4dbab6 100644 --- a/hydroroll/plugins/plugin_base/__init__.py +++ b/hydroroll/utils.py @@ -1,7 +1,6 @@ import re from abc import ABC, abstractmethod from typing import Type, Union, Generic, TypeVar - from iamai import Plugin from iamai.typing import T_State from iamai.adapter.cqhttp.event import GroupMessageEvent, PrivateMessageEvent @@ -12,7 +11,6 @@ T_Config = TypeVar("T_Config", bound=BasePluginConfig) T_RegexPluginConfig = TypeVar("T_RegexPluginConfig", bound=RegexPluginConfig) T_CommandPluginConfig = TypeVar("T_CommandPluginConfig", bound=CommandPluginConfig) - class BasePlugin( Plugin[Union[PrivateMessageEvent, GroupMessageEvent], T_State, T_Config], ABC, @@ -28,7 +26,7 @@ class BasePlugin( ) async def rule(self) -> bool: - is_bot_off = True + is_bot_off = False if self.event.adapter.name != "cqhttp": return False @@ -38,6 +36,8 @@ class BasePlugin( if is_bot_off: if self.event.message.startswith(f'[CQ:at,qq={self.event.self_id}]'): match_str = re.sub(fr'^\[CQ:at,qq={self.event.self_id}\]', '', match_str) + elif self.event.message.startswith(f'[CQ:at,qq={self.event.self_tiny_id}]'): + match_str = re.sub(fr'^\[CQ:at,qq={self.event.self_tiny_id}\]', '', match_str) else: return False if self.config.handle_all_message: @@ -52,6 +52,9 @@ class BasePlugin( or self.event.group_id in self.config.accept_group ): return self.str_match(match_str) + elif self.config.handle_group_message: + if self.event.message_type == "guild": + return self.str_match(match_str) return False @abstractmethod @@ -78,7 +81,7 @@ class CommandPluginBase(RegexPluginBase[T_State, T_CommandPluginConfig], ABC): def str_match(self, msg_str: str) -> bool: if not hasattr(self, "command_re_pattern"): self.command_re_pattern = re.compile( - f'[{"".join(self.config.command_prefix)}]' + f'({"|".join(self.config.command_prefix)})' f'({"|".join(self.config.command)})' r"\s*(?P<command_args>.*)", flags=re.I if self.config.ignore_case else 0, @@ -90,4 +93,5 @@ class CommandPluginBase(RegexPluginBase[T_State, T_CommandPluginConfig], ABC): self.msg_match = self.re_pattern.fullmatch( self.command_match.group("command_args") ) - return bool(self.msg_match)
\ No newline at end of file + return bool(self.msg_match) + |
