diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 8faed7a5..e339e2ca 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -1,6 +1,7 @@
const path = require("path");
module.exports = context => ({
+ base: process.env.VUEPRESS_BASE || "/",
title: "NoneBot",
description: "基于 酷Q 的 Python 异步 QQ 机器人框架",
markdown: {
@@ -117,12 +118,7 @@ module.exports = context => ({
title: "进阶",
collapsable: false,
sidebar: "auto",
- children: [
- "",
- "scheduler",
- "permission",
- "runtime-hook"
- ]
+ children: ["", "scheduler", "permission", "runtime-hook"]
}
],
"/api/": [
diff --git a/docs/api/nonebot.md b/docs/api/nonebot.md
index cdb72bdb..68708c95 100644
--- a/docs/api/nonebot.md
+++ b/docs/api/nonebot.md
@@ -49,9 +49,18 @@ sidebarDepth: 0
* `load_builtin_plugins` => `nonebot.plugin.load_builtin_plugins`
+* `get_plugin` => `nonebot.plugin.get_plugin`
+
+
* `get_loaded_plugins` => `nonebot.plugin.get_loaded_plugins`
+* `export` => `nonebot.plugin.export`
+
+
+* `require` => `nonebot.plugin.require`
+
+
## `get_driver()`
diff --git a/docs/api/plugin.md b/docs/api/plugin.md
index 42cdf84f..5f7a7d46 100644
--- a/docs/api/plugin.md
+++ b/docs/api/plugin.md
@@ -25,6 +25,37 @@ sidebarDepth: 0
+## _class_ `Export`
+
+基类:`dict`
+
+
+* **说明**
+
+ 插件导出内容以使得其他插件可以获得。
+
+
+
+* **示例**
+
+
+```python
+nonebot.export().default = "bar"
+
+@nonebot.export()
+def some_function():
+ pass
+
+# this don't work under python 3.9
+# use
+# export = nonebot.export(); @export.sub
+# instead
+@nonebot.export().sub
+def something_else():
+ pass
+```
+
+
## _class_ `Plugin`
基类:`object`
@@ -59,6 +90,15 @@ sidebarDepth: 0
* **说明**: 插件内定义的 `Matcher`
+### `export`
+
+
+* **类型**: `Export`
+
+
+* **说明**: 插件内定义的导出内容
+
+
## `on(type='', rule=None, permission=None, *, handlers=None, temp=False, priority=1, block=False, state=None)`
@@ -614,12 +654,35 @@ sidebarDepth: 0
+## `get_plugin(name)`
+
+
+* **说明**
+
+ 获取当前导入的某个插件。
+
+
+
+* **参数**
+
+
+ * `name: str`: 插件名,与 `load_plugin` 参数一致。如果为 `load_plugins` 导入的插件,则为文件(夹)名。
+
+
+
+* **返回**
+
+
+ * `Optional[Plugin]`
+
+
+
## `get_loaded_plugins()`
* **说明**
- 获取当前已导入的插件。
+ 获取当前已导入的所有插件。
@@ -627,3 +690,42 @@ sidebarDepth: 0
* `Set[Plugin]`
+
+
+
+## `export()`
+
+
+* **说明**
+
+ 获取插件的导出内容对象
+
+
+
+* **返回**
+
+
+ * `Export`
+
+
+
+## `require(name)`
+
+
+* **说明**
+
+ 获取一个插件的导出内容
+
+
+
+* **参数**
+
+
+ * `name: str`: 插件名,与 `load_plugin` 参数一致。如果为 `load_plugins` 导入的插件,则为文件(夹)名。
+
+
+
+* **返回**
+
+
+ * `Optional[Export]`
diff --git a/nonebot/__init__.py b/nonebot/__init__.py
index c704479e..d642ff44 100644
--- a/nonebot/__init__.py
+++ b/nonebot/__init__.py
@@ -17,7 +17,10 @@
- ``load_plugin`` => ``nonebot.plugin.load_plugin``
- ``load_plugins`` => ``nonebot.plugin.load_plugins``
- ``load_builtin_plugins`` => ``nonebot.plugin.load_builtin_plugins``
+- ``get_plugin`` => ``nonebot.plugin.get_plugin``
- ``get_loaded_plugins`` => ``nonebot.plugin.get_loaded_plugins``
+- ``export`` => ``nonebot.plugin.export``
+- ``require`` => ``nonebot.plugin.require``
"""
import importlib
@@ -239,4 +242,5 @@ async def _start_scheduler():
from nonebot.plugin import on_message, on_notice, on_request, on_metaevent, CommandGroup
from nonebot.plugin import on_startswith, on_endswith, on_keyword, on_command, on_regex
-from nonebot.plugin import load_plugin, load_plugins, load_builtin_plugins, get_loaded_plugins
+from nonebot.plugin import load_plugin, load_plugins, load_builtin_plugins
+from nonebot.plugin import export, require, get_plugin, get_loaded_plugins
diff --git a/nonebot/adapters/cqhttp/__init__.py b/nonebot/adapters/cqhttp/__init__.py
index 88b71820..714e9f62 100644
--- a/nonebot/adapters/cqhttp/__init__.py
+++ b/nonebot/adapters/cqhttp/__init__.py
@@ -112,9 +112,17 @@ async def _check_reply(bot: "Bot", event: "Event"):
return
msg_seg = event.message[index]
event.reply = await bot.get_msg(message_id=msg_seg.data["id"])
- if event.reply["sender"]["user_id"] == event.self_id:
+ # ensure string comparation
+ if str(event.reply["sender"]["user_id"]) == str(event.self_id):
event.to_me = True
del event.message[index]
+ if len(event.message) > index and event.message[index].type == "at":
+ del event.message[index]
+ if len(event.message) > index and event.message[index].type == "text":
+ event.message[index].data["text"] = event.message[index].data[
+ "text"].lstrip()
+ if not event.message[index].data["text"]:
+ del event.message[index]
if not event.message:
event.message.append(MessageSegment.text(""))
@@ -142,14 +150,14 @@ def _check_at_me(bot: "Bot", event: "Event"):
if event.message[0] == at_me_seg:
event.to_me = True
del event.message[0]
- if event.message[0].type == "text":
+ if event.message and event.message[0].type == "text":
event.message[0].data["text"] = event.message[0].data[
"text"].lstrip()
if not event.message[0].data["text"]:
del event.message[0]
- if event.message[0] == at_me_seg:
+ if event.message and event.message[0] == at_me_seg:
del event.message[0]
- if event.message[0].type == "text":
+ if event.message and event.message[0].type == "text":
event.message[0].data["text"] = event.message[0].data[
"text"].lstrip()
if not event.message[0].data["text"]:
diff --git a/nonebot/message.py b/nonebot/message.py
index 90af9a96..1b102633 100644
--- a/nonebot/message.py
+++ b/nonebot/message.py
@@ -92,8 +92,9 @@ async def _check_matcher(priority: int, bot: Bot, event: Event,
async def _check(Matcher: Type[Matcher], bot: Bot, event: Event,
state: dict) -> Optional[Type[Matcher]]:
try:
- if await Matcher.check_perm(
- bot, event) and await Matcher.check_rule(bot, event, state):
+ if (not Matcher.expire_time or datetime.now() <= Matcher.expire_time
+ ) and await Matcher.check_perm(
+ bot, event) and await Matcher.check_rule(bot, event, state):
return Matcher
except Exception as e:
logger.opt(colors=True, exception=e).error(
@@ -149,6 +150,8 @@ async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event,
try:
logger.debug(f"Running matcher {matcher}")
await matcher.run(bot, event, state)
+ except StopPropagation as e:
+ exception = e
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"Running matcher {matcher} failed."
@@ -166,7 +169,7 @@ async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event,
"Error when running RunPostProcessors"
)
- if matcher.block:
+ if matcher.block or isinstance(exception, StopPropagation):
raise StopPropagation
diff --git a/nonebot/plugin.py b/nonebot/plugin.py
index 32be4dbc..f063726c 100644
--- a/nonebot/plugin.py
+++ b/nonebot/plugin.py
@@ -11,6 +11,7 @@ import pkgutil
import importlib
from dataclasses import dataclass
from importlib._bootstrap import _load
+from contextvars import Context, ContextVar, copy_context
from nonebot.log import logger
from nonebot.matcher import Matcher
@@ -25,7 +26,49 @@ plugins: Dict[str, "Plugin"] = {}
:说明: 已加载的插件
"""
-_tmp_matchers: Set[Type[Matcher]] = set()
+_tmp_matchers: ContextVar[Set[Type[Matcher]]] = ContextVar("_tmp_matchers")
+_export: ContextVar["Export"] = ContextVar("_export")
+
+
+class Export(dict):
+ """
+ :说明:
+ 插件导出内容以使得其他插件可以获得。
+ :示例:
+
+ .. code-block:: python
+
+ nonebot.export().default = "bar"
+
+ @nonebot.export()
+ def some_function():
+ pass
+
+ # this don't work under python 3.9
+ # use
+ # export = nonebot.export(); @export.sub
+ # instead
+ @nonebot.export().sub
+ def something_else():
+ pass
+ """
+
+ def __call__(self, func, **kwargs):
+ self[func.__name__] = func
+ self.update(kwargs)
+ return func
+
+ def __setitem__(self, key, value):
+ super().__setitem__(key,
+ Export(value) if isinstance(value, dict) else value)
+
+ def __setattr__(self, name, value):
+ self[name] = Export(value) if isinstance(value, dict) else value
+
+ def __getattr__(self, name):
+ if name not in self:
+ self[name] = Export()
+ return self[name]
@dataclass(eq=False)
@@ -46,6 +89,11 @@ class Plugin(object):
- **类型**: ``Set[Type[Matcher]]``
- **说明**: 插件内定义的 ``Matcher``
"""
+ export: Export
+ """
+ - **类型**: ``Export``
+ - **说明**: 插件内定义的导出内容
+ """
def on(type: str = "",
@@ -80,7 +128,7 @@ def on(type: str = "",
block=block,
handlers=handlers,
default_state=state)
- _tmp_matchers.add(matcher)
+ _tmp_matchers.get().add(matcher)
return matcher
@@ -112,7 +160,7 @@ def on_metaevent(rule: Optional[Union[Rule, RuleChecker]] = None,
block=block,
handlers=handlers,
default_state=state)
- _tmp_matchers.add(matcher)
+ _tmp_matchers.get().add(matcher)
return matcher
@@ -146,7 +194,7 @@ def on_message(rule: Optional[Union[Rule, RuleChecker]] = None,
block=block,
handlers=handlers,
default_state=state)
- _tmp_matchers.add(matcher)
+ _tmp_matchers.get().add(matcher)
return matcher
@@ -178,7 +226,7 @@ def on_notice(rule: Optional[Union[Rule, RuleChecker]] = None,
block=block,
handlers=handlers,
default_state=state)
- _tmp_matchers.add(matcher)
+ _tmp_matchers.get().add(matcher)
return matcher
@@ -210,7 +258,7 @@ def on_request(rule: Optional[Union[Rule, RuleChecker]] = None,
block=block,
handlers=handlers,
default_state=state)
- _tmp_matchers.add(matcher)
+ _tmp_matchers.get().add(matcher)
return matcher
@@ -387,27 +435,35 @@ def load_plugin(module_path: str) -> Optional[Plugin]:
:返回:
- ``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"
+
+ def _load_plugin(module_path: str) -> Optional[Plugin]:
+ try:
+ _tmp_matchers.set(set())
+ _export.set(Export())
+ 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.get():
+ m.module = module_path
+ plugin = Plugin(module_path, module, _tmp_matchers.get(),
+ _export.get())
+ plugins[module_path] = plugin
+ logger.opt(
+ colors=True).info(f'Succeeded to import "{module_path}"')
+ return plugin
+ except Exception as e:
+ logger.opt(colors=True, exception=e).error(
+ f'Failed to import "{module_path}"'
)
- 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.opt(
- colors=True).info(f'Succeeded to import "{module_path}"')
- return plugin
- except Exception as e:
- logger.opt(colors=True, exception=e).error(
- f'Failed to import "{module_path}"')
- return None
+ return None
+
+ context: Context = copy_context()
+ return context.run(_load_plugin, module_path)
def load_plugins(*plugin_dir: str) -> Set[Plugin]:
@@ -419,33 +475,42 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
:返回:
- ``Set[Plugin]``
"""
- loaded_plugins = set()
- for module_info in pkgutil.iter_modules(plugin_dir):
- _tmp_matchers.clear()
+
+ def _load_plugin(module_info) -> Optional[Plugin]:
+ _tmp_matchers.set(set())
+ _export.set(Export())
name = module_info.name
if name.startswith("_"):
- continue
+ return
spec = module_info.module_finder.find_spec(name, None)
if spec.name in plugins:
- continue
+ return
elif spec.name in sys.modules:
logger.warning(
f"Module {spec.name} has been loaded by other plugin! Ignored")
- continue
+ return
try:
module = _load(spec)
- for m in _tmp_matchers:
+ for m in _tmp_matchers.get():
m.module = name
- plugin = Plugin(name, module, _tmp_matchers.copy())
+ plugin = Plugin(name, module, _tmp_matchers.get(), _export.get())
plugins[name] = plugin
- loaded_plugins.add(plugin)
logger.opt(colors=True).info(f'Succeeded to import "{name}"')
+ return plugin
except Exception as e:
logger.opt(colors=True, exception=e).error(
f'Failed to import "{name}"')
+ return None
+
+ loaded_plugins = set()
+ for module_info in pkgutil.iter_modules(plugin_dir):
+ context: Context = copy_context()
+ result = context.run(_load_plugin, module_info)
+ if result:
+ loaded_plugins.add(result)
return loaded_plugins
@@ -459,11 +524,46 @@ def load_builtin_plugins() -> Optional[Plugin]:
return load_plugin("nonebot.plugins.base")
+def get_plugin(name: str) -> Optional[Plugin]:
+ """
+ :说明:
+ 获取当前导入的某个插件。
+ :参数:
+ * ``name: str``: 插件名,与 ``load_plugin`` 参数一致。如果为 ``load_plugins`` 导入的插件,则为文件(夹)名。
+ :返回:
+ - ``Optional[Plugin]``
+ """
+ return plugins.get(name)
+
+
def get_loaded_plugins() -> Set[Plugin]:
"""
:说明:
- 获取当前已导入的插件。
+ 获取当前已导入的所有插件。
:返回:
- ``Set[Plugin]``
"""
return set(plugins.values())
+
+
+def export() -> Export:
+ """
+ :说明:
+ 获取插件的导出内容对象
+ :返回:
+ - ``Export``
+ """
+ return _export.get()
+
+
+def require(name: str) -> Optional[Export]:
+ """
+ :说明:
+ 获取一个插件的导出内容
+ :参数:
+ * ``name: str``: 插件名,与 ``load_plugin`` 参数一致。如果为 ``load_plugins`` 导入的插件,则为文件(夹)名。
+ :返回:
+ - ``Optional[Export]``
+ """
+ plugin = get_plugin(name)
+ return plugin.export if plugin else None
diff --git a/nonebot/plugin.pyi b/nonebot/plugin.pyi
index 6cf93b97..37d775d6 100644
--- a/nonebot/plugin.pyi
+++ b/nonebot/plugin.pyi
@@ -1,17 +1,32 @@
import re
+from contextvars import ContextVar
from nonebot.typing import Rule, Matcher, Handler, Permission, RuleChecker
from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType
plugins: Dict[str, "Plugin"] = ...
-_tmp_matchers: Set[Type[Matcher]] = ...
+_tmp_matchers: ContextVar[Set[Type[Matcher]]] = ...
+_export: ContextVar["Export"] = ...
+
+
+class Export(dict):
+
+ def __call__(self, func, **kwargs):
+ ...
+
+ def __setattr__(self, name, value):
+ ...
+
+ def __getattr__(self, name):
+ ...
class Plugin(object):
name: str
module: ModuleType
matcher: Set[Type[Matcher]]
+ export: Export
def on(type: str = ...,
@@ -141,10 +156,22 @@ def load_builtin_plugins():
...
+def get_plugin(name: str) -> Optional[Plugin]:
+ ...
+
+
def get_loaded_plugins() -> Set[Plugin]:
...
+def export() -> Export:
+ ...
+
+
+def require(name: str) -> Export:
+ ...
+
+
class CommandGroup:
def __init__(self,
diff --git a/pages/changelog.md b/pages/changelog.md
index ef8bad8f..859be509 100644
--- a/pages/changelog.md
+++ b/pages/changelog.md
@@ -4,6 +4,14 @@ sidebar: auto
# 更新日志
+## v2.0.0a7
+
+- 修复 cqhttp 检查 to me 时出现 IndexError
+- 修复已失效的事件响应器仍会运行一次的 bug
+- 修改 cqhttp 检查 reply 时未去除后续 at 以及空格
+- 添加 get_plugin 获取插件函数
+- 添加插件 export, require 方法
+
## v2.0.0a6
- 修复 block 失效问题 (hotfix)
diff --git a/tests/bot.py b/tests/bot.py
index 7a294564..68a4e399 100644
--- a/tests/bot.py
+++ b/tests/bot.py
@@ -22,6 +22,8 @@ nonebot.load_builtin_plugins()
# load local plugins
nonebot.load_plugins("test_plugins")
+print(nonebot.require("test_export"))
+
# modify some config / config depends on loaded configs
config = nonebot.get_driver().config
config.custom_config3 = config.custom_config1
diff --git a/tests/test_plugins/test_export.py b/tests/test_plugins/test_export.py
new file mode 100644
index 00000000..ec549571
--- /dev/null
+++ b/tests/test_plugins/test_export.py
@@ -0,0 +1,15 @@
+import nonebot
+
+export = nonebot.export()
+export.foo = "bar"
+export["bar"] = "foo"
+
+
+@export
+def a():
+ pass
+
+
+@export.sub
+def b():
+ pass