From 21db23168fad97fb97e6c3f70619082df6339f8d Mon Sep 17 00:00:00 2001 From: Richard Chien Date: Sat, 21 Jul 2018 00:46:34 +0800 Subject: [PATCH] Add lots of comments and logs --- none/__init__.py | 47 +++++++++++++++------------------------- none/argparse.py | 5 +++++ none/command.py | 20 +++++++++++++++-- none/default_config.py | 16 ++++++++++++++ none/log.py | 7 ++++++ none/message.py | 17 +++++++++++++-- none/natural_language.py | 6 ++++- none/notice_request.py | 10 +++++++++ 8 files changed, 94 insertions(+), 34 deletions(-) diff --git a/none/__init__.py b/none/__init__.py index 1284f415..081cddd6 100644 --- a/none/__init__.py +++ b/none/__init__.py @@ -16,9 +16,11 @@ class NoneBot(CQHttp): if config_object is None: from . import default_config as config_object - super_kwargs = {k.lower(): v for k, v in config_object.__dict__.items() - if k.isupper() and not k.startswith('_')} - super().__init__(message_class=Message, **super_kwargs) + config_dict = {k: v for k, v in config_object.__dict__.items() + if k.isupper() and not k.startswith('_')} + logger.debug(f'Loaded configurations: {config_dict}') + super().__init__(message_class=Message, + **{k.lower(): v for k, v in config_dict.items()}) self.config = config_object self.asgi.debug = self.config.DEBUG @@ -39,29 +41,10 @@ class NoneBot(CQHttp): asyncio.ensure_future(handle_notice_or_request(self, ctx)) def run(self, host=None, port=None, *args, **kwargs): + logger.info(f'Running on {host}:{port}') super().run(host=host, port=port, loop=asyncio.get_event_loop(), *args, **kwargs) - def get_data_folder(self, - *sub_folder: str) -> Optional[str]: - folder = self.config.DATA_FOLDER - if not folder: - return None - - if sub_folder: - folder = os.path.join(folder, *sub_folder) - - if not os.path.isdir(folder): - os.makedirs(folder, 0o755, exist_ok=True) - return folder - - def get_data_file(self, path: str, *others: str) -> Optional[str]: - rel_path = os.path.join(path, *others) - parent = self.get_data_folder(os.path.dirname(rel_path)) - if not parent: - return None - return os.path.join(parent, os.path.basename(rel_path)) - _bot: Optional[NoneBot] = None @@ -103,11 +86,14 @@ def run(host: str = None, port: int = None, *args, **kwargs) -> None: _plugins = set() -def clear_plugins() -> None: - _plugins.clear() - - def load_plugins(plugin_dir: str, module_prefix: str) -> None: + """ + Find all non-hidden modules or packages in a given directory, + and import them with the given module prefix. + + :param plugin_dir: plugin directory to search + :param module_prefix: module prefix used while importing + """ for name in os.listdir(plugin_dir): path = os.path.join(plugin_dir, name) if os.path.isfile(path) and \ @@ -125,12 +111,15 @@ def load_plugins(plugin_dir: str, module_prefix: str) -> None: mod_name = f'{module_prefix}.{m.group(1)}' try: _plugins.add(importlib.import_module(mod_name)) - logger.info('Succeeded to import "{}"'.format(mod_name)) + logger.info(f'Succeeded to import "{mod_name}"') except ImportError: - logger.warning('Failed to import "{}"'.format(mod_name)) + logger.warning(f'Failed to import "{mod_name}"') def load_builtin_plugins() -> None: + """ + Load built-in plugins distributed along with "none" package. + """ plugin_dir = os.path.join(os.path.dirname(__file__), 'plugins') load_plugins(plugin_dir, 'none.plugins') diff --git a/none/argparse.py b/none/argparse.py index 763094e5..a5a6a024 100644 --- a/none/argparse.py +++ b/none/argparse.py @@ -8,6 +8,11 @@ class ParserExit(RuntimeError): class ArgumentParser(ArgumentParser): + """ + An ArgumentParser wrapper that avoid printing messages to + standard I/O. + """ + def _print_message(self, *args, **kwargs): # do nothing pass diff --git a/none/command.py b/none/command.py index c7d6e004..5dc18843 100644 --- a/none/command.py +++ b/none/command.py @@ -8,6 +8,7 @@ from typing import ( from aiocqhttp.message import Message from . import NoneBot, permission as perm +from .log import logger from .expression import render from .helpers import context_id from .session import BaseSession @@ -26,8 +27,8 @@ _sessions = {} class Command: - __slots__ = ( - 'name', 'func', 'permission', 'only_to_me', 'args_parser_func') + __slots__ = ('name', 'func', 'permission', + 'only_to_me', 'args_parser_func') def __init__(self, *, name: Tuple[str], func: Callable, permission: int, only_to_me: bool): @@ -266,6 +267,8 @@ def parse_command(bot: NoneBot, :param cmd_string: command string :return: (Command object, current arg string) """ + logger.debug(f'Parsing command: {cmd_string}') + matched_start = None for start in bot.config.COMMAND_START: # loop through COMMAND_START to find the longest matched start @@ -286,8 +289,11 @@ def parse_command(bot: NoneBot, if matched_start is None: # it's not a command + logger.debug('It\'s not a command') return None, None + logger.debug(f'Matched command start: ' + f'{matched_start}{"(space)" if not matched_start else ""}') full_command = cmd_string[len(matched_start):].lstrip() if not full_command: @@ -314,10 +320,13 @@ def parse_command(bot: NoneBot, if not cmd_name: cmd_name = (cmd_name_text,) + logger.debug(f'Split command name: {cmd_name}') cmd = _find_command(cmd_name) if not cmd: + logger.debug(f'Command {cmd_name} not found') return None, None + logger.debug(f'Command {cmd.name} found, function: {cmd.func}') return cmd, ''.join(cmd_remained) @@ -337,11 +346,13 @@ async def handle_command(bot: NoneBot, ctx: Dict[str, Any]) -> bool: if _sessions.get(ctx_id): session = _sessions[ctx_id] if session and session.is_valid: + logger.debug(f'Session of command {session.cmd.name} exists') session.refresh(ctx, current_arg=str(ctx['message'])) # there is no need to check permission for existing session check_perm = False else: # the session is expired, remove it + logger.debug(f'Session of command {session.cmd.name} is expired') del _sessions[ctx_id] session = None if not session: @@ -349,6 +360,7 @@ async def handle_command(bot: NoneBot, ctx: Dict[str, Any]) -> bool: if not cmd or cmd.only_to_me and not ctx['to_me']: return False session = CommandSession(bot, ctx, cmd, current_arg=current_arg) + logger.debug(f'New session of command {session.cmd.name} created') return await _real_run_command(session, ctx_id, check_perm=check_perm) @@ -395,6 +407,7 @@ async def _real_run_command(session: CommandSession, # override session only when not disabling interaction _sessions[ctx_id] = session try: + logger.debug(f'Running command {session.cmd.name}') res = await session.cmd.run(session, **kwargs) if not disable_interaction: # the command is finished, remove the session @@ -404,8 +417,11 @@ async def _real_run_command(session: CommandSession, if disable_interaction: # if the command needs further interaction, we view it as failed return False + logger.debug(f'Further interaction needed for ' + f'command {session.cmd.name}') session.last_interaction = datetime.now() # return True because this step of the session is successful return True except _FinishException: + logger.debug(f'Session of command {session.cmd.name} finished') return True diff --git a/none/default_config.py b/none/default_config.py index a3d6e152..0c8a092b 100644 --- a/none/default_config.py +++ b/none/default_config.py @@ -1,3 +1,19 @@ +""" +Default configurations. + +Any derived configurations must import everything from this module +at the very beginning of their code, and then set their own value +to override the default one. + +For example: + +>>> from none.default_config import * +>>> PORT = 9090 +>>> DEBUG = False +>>> SUPERUSERS.add(123456) +>>> NICKNAME = '小明' +""" + import os from datetime import timedelta diff --git a/none/log.py b/none/log.py index 6aab710b..6ecc3966 100644 --- a/none/log.py +++ b/none/log.py @@ -1,3 +1,10 @@ +""" +Provide logger object. + +Any other modules in "none" should use "logger" from this module +to log messages. +""" + import logging import sys diff --git a/none/message.py b/none/message.py index ba3eda17..53676f49 100644 --- a/none/message.py +++ b/none/message.py @@ -9,6 +9,8 @@ from .natural_language import handle_natural_language async def handle_message(bot: NoneBot, ctx: Dict[str, Any]) -> None: + _log_message(ctx) + if ctx['message_type'] != 'private': # group or discuss ctx['to_me'] = False @@ -23,10 +25,21 @@ async def handle_message(bot: NoneBot, ctx: Dict[str, Any]) -> None: handled = await handle_command(bot, ctx) if handled: - logger.debug('Message is handled as a command') + logger.info(f'Message {ctx["message_id"]} is handled as a command') return handled = await handle_natural_language(bot, ctx) if handled: - logger.debug('Message is handled as natural language') + logger.info(f'Message {ctx["message_id"]} is handled ' + f'as natural language') return + + +def _log_message(ctx: Dict[str, Any]) -> None: + msg_from = f'{ctx["user_id"]}' + if ctx['message_type'] == 'group': + msg_from += f'@[群:{ctx["group_id"]}]' + elif ctx['message_type'] == 'discuss': + msg_from += f'@[讨论组:{ctx["discuss_id"]}]' + logger.info(f'Message {ctx["message_id"]} from {msg_from}: ' + f'{ctx["message"]}') diff --git a/none/natural_language.py b/none/natural_language.py index b35aa26a..146e49b1 100644 --- a/none/natural_language.py +++ b/none/natural_language.py @@ -89,6 +89,7 @@ async def handle_natural_language(bot: NoneBot, ctx: Dict[str, Any]) -> bool: nicknames = filter(lambda n: n, bot.config.NICKNAME) m = re.search(rf'^({"|".join(nicknames)})[\s,,]+', msg) if m: + logger.debug(f'User is calling me {m.group(1)}') ctx['to_me'] = True msg = msg[m.end():] @@ -114,10 +115,13 @@ async def handle_natural_language(bot: NoneBot, ctx: Dict[str, Any]) -> bool: # wait for possible results, and sort them by confidence results = sorted(filter(lambda r: r, await asyncio.gather(*coros)), key=lambda r: r.confidence, reverse=True) - logger.debug(results) + logger.debug(f'NLP results: {results}') if results and results[0].confidence >= 60.0: # choose the result with highest confidence + logger.debug(f'NLP result with highest confidence: {results[0]}') return await call_command(bot, ctx, results[0].cmd_name, args=results[0].cmd_args, check_perm=False) + else: + logger.debug('No NLP result having enough confidence') return False diff --git a/none/notice_request.py b/none/notice_request.py index db239978..1147f045 100644 --- a/none/notice_request.py +++ b/none/notice_request.py @@ -76,9 +76,19 @@ async def handle_notice_or_request(bot: NoneBot, ctx: Dict[str, Any]) -> None: event += f'.{ctx["sub_type"]}' if post_type == 'notice': + _log_notice(ctx) session = NoticeSession(bot, ctx) else: # must be 'request' + _log_request(ctx) session = RequestSession(bot, ctx) logger.debug(f'Emitting event: {event}') await _bus.emit(event, session) + + +def _log_notice(ctx: Dict[str, Any]) -> None: + logger.info(f'Notice: {ctx}') + + +def _log_request(ctx: Dict[str, Any]) -> None: + logger.info(f'Request: {ctx}')