mirror of
https://github.com/nonebot/nonebot2.git
synced 2024-11-24 09:05:04 +08:00
🔀 Merge pull request #8 from nonebot/dev
This commit is contained in:
commit
0a8a53a764
@ -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`
|
||||
|
@ -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,15 +145,20 @@ def init(*, _env_file: Optional[str] = None, **kwargs):
|
||||
|
||||
"""
|
||||
global _driver
|
||||
if not _driver:
|
||||
logger.debug("NoneBot is initializing...")
|
||||
env = Env()
|
||||
logger.debug(f"Current Env: {env.environment}")
|
||||
config = Config(**kwargs, _env_file=_env_file or f".env.{env.environment}")
|
||||
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")
|
||||
DriverClass: Type[Driver] = getattr(
|
||||
importlib.import_module(config.driver), "Driver")
|
||||
_driver = DriverClass(env, config)
|
||||
|
||||
# register build-in adapters
|
||||
@ -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
|
||||
|
@ -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]:
|
||||
|
@ -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
|
||||
|
@ -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 请求超时时间,单位: 秒。
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
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
|
||||
|
@ -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
10
nonebot/plugins/base.py
Normal 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)
|
@ -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
51
poetry.lock
generated
@ -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"},
|
||||
|
@ -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]]
|
||||
|
12
tests/bot.py
12
tests/bot.py
@ -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__":
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user