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:
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('_')}
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.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')

View File

@ -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

View File

@ -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

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
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 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:
_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"]}')

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)
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

View File

@ -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}')