Add lots of comments and logs

This commit is contained in:
Richard Chien 2018-07-21 00:46:34 +08:00
parent 1c78eb1b80
commit 21db23168f
8 changed files with 94 additions and 34 deletions

View File

@ -16,9 +16,11 @@ class NoneBot(CQHttp):
if config_object is None: if config_object is None:
from . import default_config as config_object from . import default_config as config_object
super_kwargs = {k.lower(): v for k, v in config_object.__dict__.items() config_dict = {k: v for k, v in config_object.__dict__.items()
if k.isupper() and not k.startswith('_')} if k.isupper() and not k.startswith('_')}
super().__init__(message_class=Message, **super_kwargs) 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.config = config_object
self.asgi.debug = self.config.DEBUG self.asgi.debug = self.config.DEBUG
@ -39,29 +41,10 @@ class NoneBot(CQHttp):
asyncio.ensure_future(handle_notice_or_request(self, ctx)) asyncio.ensure_future(handle_notice_or_request(self, ctx))
def run(self, host=None, port=None, *args, **kwargs): 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(), super().run(host=host, port=port, loop=asyncio.get_event_loop(),
*args, **kwargs) *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 _bot: Optional[NoneBot] = None
@ -103,11 +86,14 @@ def run(host: str = None, port: int = None, *args, **kwargs) -> None:
_plugins = set() _plugins = set()
def clear_plugins() -> None:
_plugins.clear()
def load_plugins(plugin_dir: str, module_prefix: str) -> None: 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): for name in os.listdir(plugin_dir):
path = os.path.join(plugin_dir, name) path = os.path.join(plugin_dir, name)
if os.path.isfile(path) and \ 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)}' mod_name = f'{module_prefix}.{m.group(1)}'
try: try:
_plugins.add(importlib.import_module(mod_name)) _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: except ImportError:
logger.warning('Failed to import "{}"'.format(mod_name)) logger.warning(f'Failed to import "{mod_name}"')
def load_builtin_plugins() -> None: def load_builtin_plugins() -> None:
"""
Load built-in plugins distributed along with "none" package.
"""
plugin_dir = os.path.join(os.path.dirname(__file__), 'plugins') plugin_dir = os.path.join(os.path.dirname(__file__), 'plugins')
load_plugins(plugin_dir, 'none.plugins') load_plugins(plugin_dir, 'none.plugins')

View File

@ -8,6 +8,11 @@ class ParserExit(RuntimeError):
class ArgumentParser(ArgumentParser): class ArgumentParser(ArgumentParser):
"""
An ArgumentParser wrapper that avoid printing messages to
standard I/O.
"""
def _print_message(self, *args, **kwargs): def _print_message(self, *args, **kwargs):
# do nothing # do nothing
pass pass

View File

@ -8,6 +8,7 @@ from typing import (
from aiocqhttp.message import Message from aiocqhttp.message import Message
from . import NoneBot, permission as perm from . import NoneBot, permission as perm
from .log import logger
from .expression import render from .expression import render
from .helpers import context_id from .helpers import context_id
from .session import BaseSession from .session import BaseSession
@ -26,8 +27,8 @@ _sessions = {}
class Command: class Command:
__slots__ = ( __slots__ = ('name', 'func', 'permission',
'name', 'func', 'permission', 'only_to_me', 'args_parser_func') 'only_to_me', 'args_parser_func')
def __init__(self, *, name: Tuple[str], func: Callable, permission: int, def __init__(self, *, name: Tuple[str], func: Callable, permission: int,
only_to_me: bool): only_to_me: bool):
@ -266,6 +267,8 @@ def parse_command(bot: NoneBot,
:param cmd_string: command string :param cmd_string: command string
:return: (Command object, current arg string) :return: (Command object, current arg string)
""" """
logger.debug(f'Parsing command: {cmd_string}')
matched_start = None matched_start = None
for start in bot.config.COMMAND_START: for start in bot.config.COMMAND_START:
# loop through COMMAND_START to find the longest matched 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: if matched_start is None:
# it's not a command # it's not a command
logger.debug('It\'s not a command')
return None, None 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() full_command = cmd_string[len(matched_start):].lstrip()
if not full_command: if not full_command:
@ -314,10 +320,13 @@ def parse_command(bot: NoneBot,
if not cmd_name: if not cmd_name:
cmd_name = (cmd_name_text,) cmd_name = (cmd_name_text,)
logger.debug(f'Split command name: {cmd_name}')
cmd = _find_command(cmd_name) cmd = _find_command(cmd_name)
if not cmd: if not cmd:
logger.debug(f'Command {cmd_name} not found')
return None, None return None, None
logger.debug(f'Command {cmd.name} found, function: {cmd.func}')
return cmd, ''.join(cmd_remained) 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): if _sessions.get(ctx_id):
session = _sessions[ctx_id] session = _sessions[ctx_id]
if session and session.is_valid: if session and session.is_valid:
logger.debug(f'Session of command {session.cmd.name} exists')
session.refresh(ctx, current_arg=str(ctx['message'])) session.refresh(ctx, current_arg=str(ctx['message']))
# there is no need to check permission for existing session # there is no need to check permission for existing session
check_perm = False check_perm = False
else: else:
# the session is expired, remove it # the session is expired, remove it
logger.debug(f'Session of command {session.cmd.name} is expired')
del _sessions[ctx_id] del _sessions[ctx_id]
session = None session = None
if not session: 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']: if not cmd or cmd.only_to_me and not ctx['to_me']:
return False return False
session = CommandSession(bot, ctx, cmd, current_arg=current_arg) 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) 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 # override session only when not disabling interaction
_sessions[ctx_id] = session _sessions[ctx_id] = session
try: try:
logger.debug(f'Running command {session.cmd.name}')
res = await session.cmd.run(session, **kwargs) res = await session.cmd.run(session, **kwargs)
if not disable_interaction: if not disable_interaction:
# the command is finished, remove the session # the command is finished, remove the session
@ -404,8 +417,11 @@ async def _real_run_command(session: CommandSession,
if disable_interaction: if disable_interaction:
# if the command needs further interaction, we view it as failed # if the command needs further interaction, we view it as failed
return False return False
logger.debug(f'Further interaction needed for '
f'command {session.cmd.name}')
session.last_interaction = datetime.now() session.last_interaction = datetime.now()
# return True because this step of the session is successful # return True because this step of the session is successful
return True return True
except _FinishException: except _FinishException:
logger.debug(f'Session of command {session.cmd.name} finished')
return True return True

View File

@ -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 import os
from datetime import timedelta from datetime import timedelta

View File

@ -1,3 +1,10 @@
"""
Provide logger object.
Any other modules in "none" should use "logger" from this module
to log messages.
"""
import logging import logging
import sys import sys

View File

@ -9,6 +9,8 @@ from .natural_language import handle_natural_language
async def handle_message(bot: NoneBot, ctx: Dict[str, Any]) -> None: async def handle_message(bot: NoneBot, ctx: Dict[str, Any]) -> None:
_log_message(ctx)
if ctx['message_type'] != 'private': if ctx['message_type'] != 'private':
# group or discuss # group or discuss
ctx['to_me'] = False 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) handled = await handle_command(bot, ctx)
if handled: if handled:
logger.debug('Message is handled as a command') logger.info(f'Message {ctx["message_id"]} is handled as a command')
return return
handled = await handle_natural_language(bot, ctx) handled = await handle_natural_language(bot, ctx)
if handled: if handled:
logger.debug('Message is handled as natural language') logger.info(f'Message {ctx["message_id"]} is handled '
f'as natural language')
return 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"]}')

View File

@ -89,6 +89,7 @@ async def handle_natural_language(bot: NoneBot, ctx: Dict[str, Any]) -> bool:
nicknames = filter(lambda n: n, bot.config.NICKNAME) nicknames = filter(lambda n: n, bot.config.NICKNAME)
m = re.search(rf'^({"|".join(nicknames)})[\s,]+', msg) m = re.search(rf'^({"|".join(nicknames)})[\s,]+', msg)
if m: if m:
logger.debug(f'User is calling me {m.group(1)}')
ctx['to_me'] = True ctx['to_me'] = True
msg = msg[m.end():] 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 # wait for possible results, and sort them by confidence
results = sorted(filter(lambda r: r, await asyncio.gather(*coros)), results = sorted(filter(lambda r: r, await asyncio.gather(*coros)),
key=lambda r: r.confidence, reverse=True) key=lambda r: r.confidence, reverse=True)
logger.debug(results) logger.debug(f'NLP results: {results}')
if results and results[0].confidence >= 60.0: if results and results[0].confidence >= 60.0:
# choose the result with highest confidence # 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, return await call_command(bot, ctx, results[0].cmd_name,
args=results[0].cmd_args, args=results[0].cmd_args,
check_perm=False) check_perm=False)
else:
logger.debug('No NLP result having enough confidence')
return False return False

View File

@ -76,9 +76,19 @@ async def handle_notice_or_request(bot: NoneBot, ctx: Dict[str, Any]) -> None:
event += f'.{ctx["sub_type"]}' event += f'.{ctx["sub_type"]}'
if post_type == 'notice': if post_type == 'notice':
_log_notice(ctx)
session = NoticeSession(bot, ctx) session = NoticeSession(bot, ctx)
else: # must be 'request' else: # must be 'request'
_log_request(ctx)
session = RequestSession(bot, ctx) session = RequestSession(bot, ctx)
logger.debug(f'Emitting event: {event}') logger.debug(f'Emitting event: {event}')
await _bus.emit(event, session) 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}')