🔀 Merge pull request #8 from nonebot/dev

This commit is contained in:
Ju4tCode 2020-08-30 22:21:14 +08:00 committed by GitHub
commit 0a8a53a764
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 317 additions and 104 deletions

View File

@ -7,9 +7,9 @@ sidebarDepth: 0
## 日志
NoneBot 使用标准库 [logging](https://docs.python.org/3/library/logging.html) 来记录日志信息。
NoneBot 使用 [loguru](https://github.com/Delgan/loguru) 来记录日志信息。
自定义 logger 请参考 [logging](https://docs.python.org/3/library/logging.html) 文档。
自定义 logger 请参考 [loguru](https://github.com/Delgan/loguru) 文档。
## `logger`

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
import importlib
from nonebot.typing import Bot, Dict, Type, Union, Driver, Optional, NoReturn
@ -84,7 +83,7 @@ def get_asgi():
return driver.asgi
def get_bots() -> Dict[str, Bot]:
def get_bots() -> Union[NoReturn, Dict[str, Bot]]:
"""
:说明:
@ -109,8 +108,8 @@ def get_bots() -> Dict[str, Bot]:
return driver.bots
from nonebot.log import logger
from nonebot.config import Env, Config
from nonebot.log import logger, default_filter
from nonebot.adapters.cqhttp import Bot as CQBot
try:
@ -146,24 +145,29 @@ def init(*, _env_file: Optional[str] = None, **kwargs):
"""
global _driver
env = Env()
logger.debug(f"Current Env: {env.environment}")
config = Config(**kwargs, _env_file=_env_file or f".env.{env.environment}")
if not _driver:
logger.debug("NoneBot is initializing...")
env = Env()
logger.opt(
colors=True).debug(f"Current <y><b>Env: {env.environment}</b></y>")
config = Config(**kwargs,
_env_file=_env_file or f".env.{env.environment}")
logger.setLevel(logging.DEBUG if config.debug else logging.INFO)
logger.debug(f"Loaded config: {config.dict()}")
default_filter.level = "DEBUG" if config.debug else "INFO"
logger.opt(
colors=True).debug(f"Loaded <y><b>Config</b></y>: {config.dict()}")
DriverClass: Type[Driver] = getattr(importlib.import_module(config.driver),
"Driver")
_driver = DriverClass(env, config)
DriverClass: Type[Driver] = getattr(
importlib.import_module(config.driver), "Driver")
_driver = DriverClass(env, config)
# register build-in adapters
_driver.register_adapter("cqhttp", CQBot)
# register build-in adapters
_driver.register_adapter("cqhttp", CQBot)
# load nonebot test frontend if debug
if config.debug and nonebot_test:
logger.debug("Loading nonebot test frontend...")
nonebot_test.init()
# load nonebot test frontend if debug
if config.debug and nonebot_test:
logger.debug("Loading nonebot test frontend...")
nonebot_test.init()
def run(host: Optional[str] = None,
@ -193,7 +197,10 @@ def run(host: Optional[str] = None,
nonebot.run(host="127.0.0.1", port=8080)
"""
logger.info("Running NoneBot...")
get_driver().run(host, port, *args, **kwargs)
from nonebot.plugin import load_plugins, get_loaded_plugins
from nonebot.plugin import on_message, on_notice, on_request, on_metaevent
from nonebot.plugin import on_startswith, on_endswith, on_command, on_regex
from nonebot.plugin import load_plugin, load_plugins, load_builtin_plugins, get_loaded_plugins

View File

@ -150,6 +150,16 @@ class BaseEvent(abc.ABC):
def message(self, value) -> None:
raise NotImplementedError
@property
@abc.abstractmethod
def reply(self) -> Optional[dict]:
raise NotImplementedError
@reply.setter
@abc.abstractmethod
def reply(self, value) -> None:
raise NotImplementedError
@property
@abc.abstractmethod
def raw_message(self) -> Optional[str]:

View File

@ -27,6 +27,10 @@ from nonebot.typing import overrides, Driver, WebSocket, NoReturn
from nonebot.adapters import BaseBot, BaseEvent, BaseMessage, BaseMessageSegment
def log(level: str, message: str):
return logger.opt(colors=True).log(level, "<m>CQHTTP</m> | " + message)
def escape(s: str, *, escape_comma: bool = True) -> str:
"""
对字符串进行 CQ 码转义
@ -53,6 +57,19 @@ def _b2s(b: Optional[bool]) -> Optional[str]:
return b if b is None else str(b).lower()
async def _check_reply(bot: "Bot", event: "Event"):
if event.type != "message":
return
first_msg_seg = event.message[0]
if first_msg_seg.type == "reply":
msg_id = first_msg_seg.data["id"]
event.reply = await bot.get_msg(message_id=msg_id)
if event.reply["sender"]["user_id"] == event.self_id:
event.to_me = True
del event.message[0]
def _check_at_me(bot: "Bot", event: "Event"):
if event.type != "message":
return
@ -60,7 +77,6 @@ def _check_at_me(bot: "Bot", event: "Event"):
if event.detail_type == "private":
event.to_me = True
else:
event.to_me = False
at_me_seg = MessageSegment.at(event.self_id)
# check the first segment
@ -109,7 +125,7 @@ def _check_nickname(bot: "Bot", event: "Event"):
re.IGNORECASE)
if m:
nickname = m.group(1)
logger.debug(f"User is calling me {nickname}")
log("DEBUG", f"User is calling me {nickname}")
event.to_me = True
first_msg_seg.data["text"] = first_text[m.end():]
@ -146,7 +162,7 @@ class ResultStore:
try:
return await asyncio.wait_for(future, timeout)
except asyncio.TimeoutError:
raise NetworkError("WebSocket API call timeout")
raise NetworkError("WebSocket API call timeout") from None
finally:
del cls._futures[seq]
@ -186,7 +202,7 @@ class Bot(BaseBot):
event = Event(message)
# Check whether user is calling me
# TODO: Check reply
await _check_reply(self, event)
_check_at_me(self, event)
_check_nickname(self, event)
@ -200,7 +216,8 @@ class Bot(BaseBot):
bot = self.driver.bots[str(self_id)]
return await bot.call_api(api, **data)
if self.type == "websocket":
log("DEBUG", f"Calling API <y>{api}</y>")
if self.connection_type == "websocket":
seq = ResultStore.get_seq()
await self.websocket.send({
"action": api,
@ -212,7 +229,7 @@ class Bot(BaseBot):
return _handle_api_result(await ResultStore.fetch(
seq, self.config.api_timeout))
elif self.type == "http":
elif self.connection_type == "http":
api_root = self.config.api_root.get(self.self_id)
if not api_root:
raise ApiNotAvailable
@ -372,6 +389,16 @@ class Event(BaseEvent):
def message(self, value) -> None:
self._raw_event["message"] = value
@property
@overrides(BaseEvent)
def reply(self) -> Optional[dict]:
return self._raw_event.get("reply")
@reply.setter
@overrides(BaseEvent)
def reply(self, value) -> None:
self._raw_event["reply"] = value
@property
@overrides(BaseEvent)
def raw_message(self) -> Optional[str]:
@ -403,7 +430,7 @@ class MessageSegment(BaseMessageSegment):
@overrides(BaseMessageSegment)
def __init__(self, type: str, data: Dict[str, Union[str, list]]) -> None:
if type == "text":
data["text"] = unescape(data["text"])
data["text"] = unescape(data["text"]) # type: ignore
super().__init__(type=type, data=data)
@overrides(BaseMessageSegment)
@ -413,7 +440,9 @@ class MessageSegment(BaseMessageSegment):
# process special types
if type_ == "text":
return escape(data.get("text", ""), escape_comma=False)
return escape(
data.get("text", ""), # type: ignore
escape_comma=False)
params = ",".join(
[f"{k}={escape(str(v))}" for k, v in data.items() if v is not None])
@ -449,7 +478,7 @@ class MessageSegment(BaseMessageSegment):
@staticmethod
def forward(id_: str) -> "MessageSegment":
logger.warning("Forward Message only can be received!")
log("WARNING", "Forward Message only can be received!")
return MessageSegment("forward", {"id": id_})
@staticmethod

View File

@ -174,10 +174,10 @@ class Config(BaseConfig):
API_ROOT={"123456": "http://127.0.0.1:5700"}
"""
api_timeout: Optional[float] = 60.
api_timeout: Optional[float] = 30.
"""
- 类型: ``Optional[float]``
- 默认值: ``60.``
- 默认值: ``30.``
- 说明:
API 请求超时时间单位:
"""

View File

@ -20,7 +20,8 @@ class BaseDriver(abc.ABC):
@classmethod
def register_adapter(cls, name: str, adapter: Type[Bot]):
cls._adapters[name] = adapter
logger.debug(f'Succeeded to load adapter "{name}"')
logger.opt(
colors=True).debug(f'Succeeded to load adapter "<y>{name}</y>"')
@property
@abc.abstractmethod

View File

@ -84,17 +84,9 @@ class Driver(BaseDriver):
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"()": "logging.Formatter",
"fmt": "[%(asctime)s %(name)s] %(levelname)s: %(message)s",
},
},
"handlers": {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
"class": "nonebot.log.LoguruHandler",
},
},
"loggers": {
@ -200,6 +192,9 @@ class Driver(BaseDriver):
await ws.accept()
self._clients[x_self_id] = bot
logger.opt(colors=True).info(
f"WebSocket Connection from <y>{adapter.upper()} "
f"Bot {x_self_id}</y> Accepted!")
try:
while not ws.closed:

View File

@ -4,18 +4,21 @@
日志
====
NoneBot 使用标准库 `logging`_ 来记录日志信息
NoneBot 使用 `loguru`_ 来记录日志信息
自定义 logger 请参考 `logging`_ 文档
自定义 logger 请参考 `loguru`_ 文档
.. _logging:
https://docs.python.org/3/library/logging.html
.. _loguru:
https://github.com/Delgan/loguru
"""
import sys
import logging
logger = logging.getLogger("nonebot")
from loguru import logger as logger_
# logger = logging.getLogger("nonebot")
logger = logger_
"""
:说明:
@ -32,13 +35,52 @@ logger = logging.getLogger("nonebot")
.. code-block:: python
from nonebot.log import logger
# 也可以这样
import logging
logger = logging.getLogger("nonebot")
"""
default_handler = logging.StreamHandler(sys.stdout)
default_handler.setFormatter(
logging.Formatter("[%(asctime)s %(name)s] %(levelname)s: %(message)s"))
logger.addHandler(default_handler)
# default_handler = logging.StreamHandler(sys.stdout)
# default_handler.setFormatter(
# logging.Formatter("[%(asctime)s %(name)s] %(levelname)s: %(message)s"))
# logger.addHandler(default_handler)
class Filter:
def __init__(self) -> None:
self.level = "DEBUG"
def __call__(self, record):
record["name"] = record["name"].split(".")[0]
levelno = logger.level(self.level).no
return record["level"].no >= levelno
class LoguruHandler(logging.Handler):
def emit(self, record):
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
logger.opt(depth=depth,
exception=record.exc_info).log(level, record.getMessage())
logger.remove()
default_filter = Filter()
default_format = (
"<g>{time:MM-DD HH:mm:ss}</g> "
"[<lvl>{level}</lvl>] "
"<c><u>{name}</u></c> | "
# "<c>{function}:{line}</c>| "
"{message}")
logger.add(sys.stdout,
colorize=True,
diagnose=False,
filter=default_filter,
format=default_format)

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from nonebot.log import logger
import typing
import inspect
from functools import wraps
@ -19,9 +20,21 @@ current_bot: ContextVar = ContextVar("current_bot")
current_event: ContextVar = ContextVar("current_event")
class Matcher:
class MatcherMeta(type):
def __repr__(self) -> str:
return (f"<Matcher from {self.module or 'unknow'}, " # type: ignore
f"type={self.type}, priority={self.priority}, " # type: ignore
f"temp={self.temp}>") # type: ignore
def __str__(self) -> str:
return repr(self)
class Matcher(metaclass=MatcherMeta):
"""`Matcher`类
"""
module: Optional[str] = None
type: str = ""
rule: Rule = Rule()
@ -43,8 +56,8 @@ class Matcher:
self.state = self._default_state.copy()
def __repr__(self) -> str:
return (f"<Matcher {self.type}, priority={self.priority},"
f" temp={self.temp}, expire={self.expire_time}>")
return (f"<Matcher from {self.module or 'unknow'}, type={self.type}, "
f"priority={self.priority}, temp={self.temp}>")
def __str__(self) -> str:
return self.__repr__()
@ -59,6 +72,7 @@ class Matcher:
priority: int = 1,
block: bool = False,
*,
module: Optional[str] = None,
default_state: Optional[dict] = None,
expire_time: Optional[datetime] = None) -> Type["Matcher"]:
"""创建新的 Matcher
@ -69,6 +83,7 @@ class Matcher:
NewMatcher = type(
"Matcher", (Matcher,), {
"module": module,
"type": type_,
"rule": rule,
"permission": permission,
@ -253,5 +268,6 @@ class Matcher:
except FinishedException:
pass
finally:
logger.info(f"Matcher {self} running complete")
current_bot.reset(b_t)
current_event.reset(e_t)

View File

@ -30,11 +30,10 @@ async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event,
bot, event) or not await Matcher.check_rule(bot, event, state):
return
except Exception as e:
logger.error(f"Rule check failed for matcher {Matcher}. Ignored.")
logger.exception(e)
logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>")
return
# TODO: log matcher
logger.info(f"Event will be handled by {Matcher}")
matcher = Matcher()
@ -43,8 +42,9 @@ async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event,
logger.debug(f"Running matcher {matcher}")
await matcher.run(bot, event, state)
except Exception as e:
logger.error(f"Running matcher {matcher} failed.")
logger.exception(e)
logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>"
)
exceptions = []
if Matcher.temp:
@ -56,20 +56,23 @@ async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event,
async def handle_event(bot: Bot, event: Event):
log_msg = f"{bot.type.upper()} Bot {event.self_id} [{event.name}]: "
log_msg = f"<m>{bot.type.upper()} </m>| {event.self_id} [{event.name}]: "
if event.type == "message":
log_msg += f"Message {event.id} from "
log_msg += str(event.user_id)
if event.detail_type == "group":
log_msg += f"@[群:{event.group_id}]: "
log_msg += repr(str(event.message))
log_msg += f"@[群:{event.group_id}]:"
log_msg += ' "' + "".join(
map(lambda x: str(x) if x.type == "text" else f"<le>{x!s}</le>",
event.message)) + '"' # type: ignore
elif event.type == "notice":
log_msg += f"Notice {event.raw_event}"
elif event.type == "request":
log_msg += f"Request {event.raw_event}"
elif event.type == "meta_event":
log_msg += f"MetaEvent {event.raw_event}"
logger.info(log_msg)
logger.opt(colors=True).info(log_msg)
coros = []
state = {}
@ -80,7 +83,8 @@ async def handle_event(bot: Bot, event: Event):
logger.debug("Running PreProcessors...")
await asyncio.gather(*coros)
except IgnoredException:
logger.info(f"Event {event.name} is ignored")
logger.opt(
colors=True).info(f"Event {event.name} is <b>ignored</b>")
return
# Trie Match
@ -96,7 +100,7 @@ async def handle_event(bot: Bot, event: Event):
for matcher in matchers[priority]
]
logger.debug(f"Checking for all matchers in priority {priority}...")
logger.debug(f"Checking for matchers in priority {priority}...")
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
i = 0
@ -104,8 +108,12 @@ async def handle_event(bot: Bot, event: Event):
if isinstance(result, _ExceptionContainer):
e_list = result.exceptions
if StopPropagation in e_list:
break_flag = True
logger.debug("Stop event propagation")
if not break_flag:
break_flag = True
logger.debug("Stop event propagation")
if ExpiredException in e_list:
logger.debug(
f"Matcher {matchers[priority][index - i]} will be removed."
)
del matchers[priority][index - i]
i += 1

View File

@ -5,33 +5,32 @@ import re
import sys
import pkgutil
import importlib
from dataclasses import dataclass
from importlib._bootstrap import _load
from nonebot.log import logger
from nonebot.matcher import Matcher
from nonebot.permission import Permission
from nonebot.typing import Handler, RuleChecker
from nonebot.rule import Rule, startswith, endswith, command, regex
from nonebot.typing import Set, Dict, Type, Tuple, Union, Optional, ModuleType, RuleChecker
from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType
plugins: Dict[str, "Plugin"] = {}
_tmp_matchers: Set[Type[Matcher]] = set()
@dataclass(eq=False)
class Plugin(object):
# TODO: store plugin informations
def __init__(self, module_path: str, module: ModuleType,
matchers: Set[Type[Matcher]]):
self.module_path = module_path
self.module = module
self.matchers = matchers
name: str
module: ModuleType
matcher: Set[Type[Matcher]]
def on(rule: Union[Rule, RuleChecker] = Rule(),
permission: Permission = Permission(),
*,
handlers: Optional[list] = None,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -50,7 +49,7 @@ def on(rule: Union[Rule, RuleChecker] = Rule(),
def on_metaevent(rule: Union[Rule, RuleChecker] = Rule(),
*,
handlers: Optional[list] = None,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -70,7 +69,7 @@ def on_metaevent(rule: Union[Rule, RuleChecker] = Rule(),
def on_message(rule: Union[Rule, RuleChecker] = Rule(),
permission: Permission = Permission(),
*,
handlers: Optional[list] = None,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
priority: int = 1,
block: bool = True,
@ -89,7 +88,7 @@ def on_message(rule: Union[Rule, RuleChecker] = Rule(),
def on_notice(rule: Union[Rule, RuleChecker] = Rule(),
*,
handlers: Optional[list] = None,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -108,7 +107,7 @@ def on_notice(rule: Union[Rule, RuleChecker] = Rule(),
def on_request(rule: Union[Rule, RuleChecker] = Rule(),
*,
handlers: Optional[list] = None,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -149,9 +148,19 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
**kwargs) -> Type[Matcher]:
if isinstance(cmd, str):
cmd = (cmd,)
return on_message(command(cmd) &
rule, permission, **kwargs) if rule else on_message(
command(cmd), permission, **kwargs)
async def _strip_cmd(bot, event, state: dict):
message = event.message
event.message = message.__class__(
str(message)[len(state["_prefix"]["raw_command"]):].strip())
handlers = kwargs.pop("handlers", [])
handlers.insert(0, _strip_cmd)
return on_message(
command(cmd) &
rule, permission, handlers=handlers, **kwargs) if rule else on_message(
command(cmd), permission, handlers=handlers, **kwargs)
def on_regex(pattern: str,
@ -167,14 +176,24 @@ def on_regex(pattern: str,
def load_plugin(module_path: str) -> Optional[Plugin]:
try:
_tmp_matchers.clear()
if module_path in plugins:
return plugins[module_path]
elif module_path in sys.modules:
logger.warning(
f"Module {module_path} has been loaded by other plugins! Ignored"
)
return
module = importlib.import_module(module_path)
for m in _tmp_matchers:
m.module = module_path
plugin = Plugin(module_path, module, _tmp_matchers.copy())
plugins[module_path] = plugin
logger.info(f"Succeeded to import \"{module_path}\"")
logger.opt(
colors=True).info(f'Succeeded to import "<y>{module_path}</y>"')
return plugin
except Exception as e:
logger.error(f"Failed to import \"{module_path}\", error: {e}")
logger.exception(e)
logger.opt(colors=True, exception=e).error(
f'<r><bg #f8bbd0>Failed to import "{module_path}"</bg #f8bbd0></r>')
return None
@ -187,21 +206,31 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
continue
spec = module_info.module_finder.find_spec(name)
if spec.name in sys.modules:
if spec.name in plugins:
continue
elif spec.name in sys.modules:
logger.warning(
f"Module {spec.name} has been loaded by other plugin! Ignored")
continue
try:
module = _load(spec)
for m in _tmp_matchers:
m.module = name
plugin = Plugin(name, module, _tmp_matchers.copy())
plugins[name] = plugin
loaded_plugins.add(plugin)
logger.info(f"Succeeded to import \"{name}\"")
logger.opt(colors=True).info(f'Succeeded to import "<y>{name}</y>"')
except Exception as e:
logger.error(f"Failed to import \"{name}\", error: {e}")
logger.exception(e)
logger.opt(colors=True, exception=e).error(
f'<r><bg #f8bbd0>Failed to import "{name}"</bg #f8bbd0></r>')
return loaded_plugins
def load_builtin_plugins():
return load_plugin("nonebot.plugins.base")
def get_loaded_plugins() -> Set[Plugin]:
return set(plugins.values())

10
nonebot/plugins/base.py Normal file
View File

@ -0,0 +1,10 @@
from nonebot.rule import to_me
from nonebot.plugin import on_command
from nonebot.typing import Bot, Event
say = on_command("say", to_me())
@say.handle()
async def repeat(bot: Bot, event: Event, state: dict):
await bot.send(message=event.message, event=event)

View File

@ -74,13 +74,21 @@ class TrieRule:
suffix = cls.suffix.longest_prefix(
message_r.data["text"].rstrip()[::-1])
state["_prefix"] = {prefix.key: prefix.value} if prefix else {}
state["_suffix"] = {suffix.key: suffix.value} if suffix else {}
state["_prefix"] = {
"raw_command": prefix.key,
"command": prefix.value
} if prefix else {}
state["_suffix"] = {
"raw_command": suffix.key,
"command": suffix.value
} if suffix else {}
return ({
prefix.key: prefix.value
"raw_command": prefix.key,
"command": prefix.value
} if prefix else {}, {
suffix.key: suffix.value
"raw_command": suffix.key,
"command": suffix.value
} if suffix else {})
@ -122,7 +130,7 @@ def command(command: Tuple[str, ...]) -> Rule:
TrieRule.add_prefix(f"{start}{sep.join(command)}", command)
async def _command(bot: Bot, event: Event, state: dict) -> bool:
return command in state["_prefix"].values()
return command == state["_prefix"]["command"]
return Rule(_command)

51
poetry.lock generated
View File

@ -99,7 +99,7 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "dev"
category = "main"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama"
@ -332,6 +332,26 @@ reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "Python logging made (stupidly) simple"
name = "loguru"
optional = false
python-versions = ">=3.5"
version = "0.5.1"
[package.dependencies]
colorama = ">=0.3.4"
win32-setctime = ">=1.0.0"
[package.extras]
dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=4.3.20)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.3b0)"]
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "dev"
description = "Safely add untrusted strings to HTML/XML markup."
@ -603,6 +623,7 @@ yapf = "*"
reference = "1438d33cbeaab0230c9f7e33bd059eb9f57c86d6"
type = "git"
url = "https://github.com/nonebot/sphinx-markdown-builder.git"
[[package]]
category = "dev"
description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books"
@ -833,6 +854,23 @@ reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "main"
description = "A small Python utility to set file creation time on Windows"
marker = "sys_platform == \"win32\""
name = "win32-setctime"
optional = false
python-versions = ">=3.5"
version = "1.0.1"
[package.extras]
dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"]
[package.source]
reference = "aliyun"
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
[[package]]
category = "dev"
description = "A formatter for Python code."
@ -848,10 +886,9 @@ url = "https://mirrors.aliyun.com/pypi/simple"
[extras]
scheduler = ["apscheduler"]
test = []
[metadata]
content-hash = "4d16d7ad0930bc9851802bc149f843c4e990a987e89414d765579ea8dccc8d6e"
content-hash = "2e8f1fc9fcb89a528ecbebbf0f2315abf39e3de8eb40c133b91085a784e49173"
python-versions = "^3.7"
[metadata.files]
@ -949,6 +986,10 @@ jinja2 = [
{file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
{file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
]
loguru = [
{file = "loguru-0.5.1-py3-none-any.whl", hash = "sha256:e5d362a43cd2fc2da63551d79a6830619c4d5b3a8b976515748026f92f351b61"},
{file = "loguru-0.5.1.tar.gz", hash = "sha256:70201d5fce26da89b7a5f168caa2bb674e06b969829f56737db1d6472e53e7c3"},
]
markupsafe = [
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
@ -1136,6 +1177,10 @@ websockets = [
{file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"},
{file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"},
]
win32-setctime = [
{file = "win32_setctime-1.0.1-py3-none-any.whl", hash = "sha256:568fd636c68350bcc54755213fe01966fe0a6c90b386c0776425944a0382abef"},
{file = "win32_setctime-1.0.1.tar.gz", hash = "sha256:b47e5023ec7f0b4962950902b15bc56464a380d869f59d27dbf9ab423b23e8f9"},
]
yapf = [
{file = "yapf-0.30.0-py2.py3-none-any.whl", hash = "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"},
{file = "yapf-0.30.0.tar.gz", hash = "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427"},

View File

@ -27,15 +27,16 @@ fastapi = "^0.58.1"
uvicorn = "^0.11.5"
pydantic = { extras = ["dotenv"], version = "^1.6.1" }
apscheduler = { version = "^3.6.3", optional = true }
nonebot-test = { version = "^0.1.0", optional = true }
# nonebot-test = { version = "^0.1.0", optional = true }
loguru = "^0.5.1"
[tool.poetry.dev-dependencies]
yapf = "^0.30.0"
sphinx = "^3.1.1"
sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" }
[tool.poetry.extras]
test = ["nonebot-test"]
# test = ["nonebot-test"]
scheduler = ["apscheduler"]
[[tool.poetry.source]]

View File

@ -7,10 +7,22 @@ import sys
sys.path.insert(0, os.path.abspath(".."))
import nonebot
from nonebot.log import logger, default_format
# test custom log
logger.add("error.log",
rotation="00:00",
diagnose=False,
level="ERROR",
format=default_format)
nonebot.init()
app = nonebot.get_asgi()
# load builtin plugin
nonebot.load_plugin("nonebot.plugins.base")
# load local plugins
nonebot.load_plugins("test_plugins")
if __name__ == "__main__":

View File

@ -11,7 +11,7 @@ test_command = on_command("帮助", to_me())
@test_command.handle()
async def test_handler(bot: Bot, event: Event, state: dict):
args = str(event.message)[len(list(state["_prefix"].keys())[0]):].strip()
args = str(event.message).strip()
print("[!] Command:", state["_prefix"], "Args:", args)
if args:
state["help"] = args