diff --git a/none/__init__.py b/none/__init__.py index ddd91450..31816ac6 100644 --- a/none/__init__.py +++ b/none/__init__.py @@ -17,7 +17,7 @@ default_handler.setFormatter(logging.Formatter( logger.addHandler(default_handler) from .plugin import handle_message, handle_notice, handle_request -from .command import on_command +from .command import on_command, call_command def create_bot(config_object: Any = None): diff --git a/none/command.py b/none/command.py index 080561e2..9d648168 100644 --- a/none/command.py +++ b/none/command.py @@ -19,60 +19,74 @@ _aliases = {} _sessions = {} +# TODO: session 保存为一个栈,命令可以调用命令,进入新的 session,命令执行完毕, +# 中间没有抛出异常(标志进入交互模式的异常),则从栈中 pop + + class Command: - __slots__ = ('name', 'func', 'permission') + __slots__ = ('name', 'func', 'permission', 'args_parser') def __init__(self, name: Tuple[str], func: Callable, permission: int): self.name = name self.func = func self.permission = permission + self.args_parser = None - async def run(self, bot, session) -> bool: - permission = 0 - ctx = session.ctx - if ctx['user_id'] in bot.config.SUPERUSERS: - permission |= perm.IS_SUPERUSER - if ctx['message_type'] == 'private': - if ctx['sub_type'] == 'friend': - permission |= perm.IS_PRIVATE_FRIEND - elif ctx['sub_type'] == 'group': - permission |= perm.IS_PRIVATE_GROUP - elif ctx['sub_type'] == 'discuss': - permission |= perm.IS_PRIVATE_DISCUSS - elif ctx['sub_type'] == 'other': - permission |= perm.IS_PRIVATE_OTHER - elif ctx['message_type'] == 'group': - permission |= perm.IS_GROUP_MEMBER - if not ctx['anonymous']: - try: - member_info = await bot.get_group_member_info(**ctx) - if member_info: - if member_info['role'] == 'owner': - permission |= perm.IS_GROUP_OWNER - elif member_info['role'] == 'admin': - permission |= perm.IS_GROUP_ADMIN - except CQHttpError: - pass - elif ctx['message_type'] == 'discuss': - permission |= perm.IS_DISCUSS - + async def run(self, bot, session, *, + permission: int = None) -> bool: + if permission is None: + permission = await calculate_permission(bot, session.ctx) if isinstance(self.func, Callable) and permission & self.permission: + if isinstance(self.args_parser, Callable): + self.args_parser(session) await self.func(bot, session) return True return False -def _find_command(name: Tuple[str]) -> Optional[Command]: - if not name: +async def calculate_permission(bot: CQHttp, ctx: Dict[str, Any]) -> int: + permission = 0 + if ctx['user_id'] in bot.config.SUPERUSERS: + permission |= perm.IS_SUPERUSER + if ctx['message_type'] == 'private': + if ctx['sub_type'] == 'friend': + permission |= perm.IS_PRIVATE_FRIEND + elif ctx['sub_type'] == 'group': + permission |= perm.IS_PRIVATE_GROUP + elif ctx['sub_type'] == 'discuss': + permission |= perm.IS_PRIVATE_DISCUSS + elif ctx['sub_type'] == 'other': + permission |= perm.IS_PRIVATE_OTHER + elif ctx['message_type'] == 'group': + permission |= perm.IS_GROUP_MEMBER + if not ctx['anonymous']: + try: + member_info = await bot.get_group_member_info(**ctx) + if member_info: + if member_info['role'] == 'owner': + permission |= perm.IS_GROUP_OWNER + elif member_info['role'] == 'admin': + permission |= perm.IS_GROUP_ADMIN + except CQHttpError: + pass + elif ctx['message_type'] == 'discuss': + permission |= perm.IS_DISCUSS + return permission + + +def _find_command(name: Union[str, Tuple[str]]) -> Optional[Command]: + cmd_name = name if isinstance(name, tuple) else (name,) + + if not cmd_name: return None cmd_tree = _registry - for part in name[:-1]: + for part in cmd_name[:-1]: if part not in cmd_tree: return None cmd_tree = cmd_tree[part] - return cmd_tree.get(name[-1]) + return cmd_tree.get(cmd_name[-1]) class Session: @@ -80,15 +94,16 @@ class Session: 'current_key', 'current_arg', 'current_arg_text', 'images', 'args', 'last_interaction') - def __init__(self, cmd: Command, ctx: Dict[str, Any], - current_arg: str = ''): + def __init__(self, cmd: Command, ctx: Dict[str, Any], *, + current_arg: str = '', args: Dict[str, Any] = None): self.cmd = cmd self.ctx = ctx self.current_key = None self.current_arg = current_arg self.current_arg_text = Message(current_arg).extract_plain_text() - self.images = [] - self.args = {} + self.images = [s.data['url'] for s in ctx['message'] + if s.type == 'image' and 'url' in s.data] + self.args = args or {} self.last_interaction = None def require_arg(self, key: str, prompt: str = '', *, @@ -137,9 +152,8 @@ async def handle_command(bot: CQHttp, ctx: Dict[str, Any]) -> bool: if not cmd: return False - session = Session(cmd=cmd, ctx=ctx, current_arg=''.join(cmd_remained)) - session.images = [s.data['url'] for s in ctx['message'] - if s.type == 'image' and 'url' in s.data] + session = Session(cmd, ctx, current_arg=''.join(cmd_remained)) + # TODO: 插入 session return await cmd.run(bot, session) @@ -156,13 +170,40 @@ def on_command(name: Union[str, Tuple[str]], aliases: Iterable = (), for parent_key in cmd_name[:-1]: current_parent[parent_key] = {} current_parent = current_parent[parent_key] - current_parent[cmd_name[-1]] = Command( - name=cmd_name, func=func, permission=permission) + cmd = Command(name=cmd_name, func=func, permission=permission) + current_parent[cmd_name[-1]] = cmd for alias in aliases: _aliases[alias] = cmd_name - # TODO: 给 func 添加一个 argparser 装饰器,用于注册它的参数解析器 + def args_parser(parser_func: Callable): + cmd.args_parser = parser_func + return parser_func + func.args_parser = args_parser return func return deco + + +async def call_command(name: Union[str, Tuple[str]], + bot: CQHttp, ctx: Dict[str, Any], **kwargs) -> bool: + """ + Call a command internally. + + There is no permission restriction on this function, + which means any command can be called from any other command. + Unexpected users should be handled by the caller command's permission + option. + + :param name: command name (str or tuple of str) + :param bot: CQHttp instance + :param ctx: event context + :param kwargs: other keyword args that will be passed to Session() + :return: the command is successfully called + """ + cmd = _find_command(name) + if cmd: + session = Session(cmd, ctx, **kwargs) + # TODO: 插入 session + return await cmd.run(bot, session, permission=perm.IS_SUPERUSER) + return False diff --git a/none/permissions.py b/none/permissions.py index b72274c4..7364683f 100644 --- a/none/permissions.py +++ b/none/permissions.py @@ -11,6 +11,7 @@ GROUP = 0x0F00 SUPERUSER = 0xF000 EVERYONE = 0xFFFF +IS_NOBODY = 0x0000 IS_PRIVATE_FRIEND = PRIVATE_FRIEND IS_PRIVATE_GROUP = PRIVATE_GROUP IS_PRIVATE_DISCUSS = PRIVATE_DISCUSS diff --git a/plugins/weather.py b/plugins/weather.py new file mode 100644 index 00000000..366aceab --- /dev/null +++ b/plugins/weather.py @@ -0,0 +1,15 @@ +import none +from none.command import Session +from none.helpers import send + + +@none.on_command('weather', aliases=('天气',)) +async def weather(bot, session: Session): + city = session.require_arg('city', prompt='你想知道哪个城市的天气呢?') + await send(bot, session.ctx, f'你查询了{city}的天气') + + +@weather.args_parser +def _(session: Session): + if session.current_key: + session.args[session.current_key] = session.current_arg.strip()