🔀 Merge pull request #73

This commit is contained in:
Ju4tCode 2020-11-22 01:59:11 +08:00 committed by GitHub
commit de07a6594d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 326 additions and 52 deletions

View File

@ -1,6 +1,7 @@
const path = require("path"); const path = require("path");
module.exports = context => ({ module.exports = context => ({
base: process.env.VUEPRESS_BASE || "/",
title: "NoneBot", title: "NoneBot",
description: "基于 酷Q 的 Python 异步 QQ 机器人框架", description: "基于 酷Q 的 Python 异步 QQ 机器人框架",
markdown: { markdown: {
@ -117,12 +118,7 @@ module.exports = context => ({
title: "进阶", title: "进阶",
collapsable: false, collapsable: false,
sidebar: "auto", sidebar: "auto",
children: [ children: ["", "scheduler", "permission", "runtime-hook"]
"",
"scheduler",
"permission",
"runtime-hook"
]
} }
], ],
"/api/": [ "/api/": [

View File

@ -49,9 +49,18 @@ sidebarDepth: 0
* `load_builtin_plugins` => `nonebot.plugin.load_builtin_plugins` * `load_builtin_plugins` => `nonebot.plugin.load_builtin_plugins`
* `get_plugin` => `nonebot.plugin.get_plugin`
* `get_loaded_plugins` => `nonebot.plugin.get_loaded_plugins` * `get_loaded_plugins` => `nonebot.plugin.get_loaded_plugins`
* `export` => `nonebot.plugin.export`
* `require` => `nonebot.plugin.require`
## `get_driver()` ## `get_driver()`

View File

@ -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` ## _class_ `Plugin`
基类:`object` 基类:`object`
@ -59,6 +90,15 @@ sidebarDepth: 0
* **说明**: 插件内定义的 `Matcher` * **说明**: 插件内定义的 `Matcher`
### `export`
* **类型**: `Export`
* **说明**: 插件内定义的导出内容
## `on(type='', rule=None, permission=None, *, handlers=None, temp=False, priority=1, block=False, state=None)` ## `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()` ## `get_loaded_plugins()`
* **说明** * **说明**
获取当前已导入的插件。 获取当前已导入的所有插件。
@ -627,3 +690,42 @@ sidebarDepth: 0
* `Set[Plugin]` * `Set[Plugin]`
## `export()`
* **说明**
获取插件的导出内容对象
* **返回**
* `Export`
## `require(name)`
* **说明**
获取一个插件的导出内容
* **参数**
* `name: str`: 插件名,与 `load_plugin` 参数一致。如果为 `load_plugins` 导入的插件,则为文件(夹)名。
* **返回**
* `Optional[Export]`

View File

@ -17,7 +17,10 @@
- ``load_plugin`` => ``nonebot.plugin.load_plugin`` - ``load_plugin`` => ``nonebot.plugin.load_plugin``
- ``load_plugins`` => ``nonebot.plugin.load_plugins`` - ``load_plugins`` => ``nonebot.plugin.load_plugins``
- ``load_builtin_plugins`` => ``nonebot.plugin.load_builtin_plugins`` - ``load_builtin_plugins`` => ``nonebot.plugin.load_builtin_plugins``
- ``get_plugin`` => ``nonebot.plugin.get_plugin``
- ``get_loaded_plugins`` => ``nonebot.plugin.get_loaded_plugins`` - ``get_loaded_plugins`` => ``nonebot.plugin.get_loaded_plugins``
- ``export`` => ``nonebot.plugin.export``
- ``require`` => ``nonebot.plugin.require``
""" """
import importlib 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_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 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

View File

@ -112,9 +112,17 @@ async def _check_reply(bot: "Bot", event: "Event"):
return return
msg_seg = event.message[index] msg_seg = event.message[index]
event.reply = await bot.get_msg(message_id=msg_seg.data["id"]) 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 event.to_me = True
del event.message[index] 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: if not event.message:
event.message.append(MessageSegment.text("")) event.message.append(MessageSegment.text(""))
@ -142,14 +150,14 @@ def _check_at_me(bot: "Bot", event: "Event"):
if event.message[0] == at_me_seg: if event.message[0] == at_me_seg:
event.to_me = True event.to_me = True
del event.message[0] 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[ event.message[0].data["text"] = event.message[0].data[
"text"].lstrip() "text"].lstrip()
if not event.message[0].data["text"]: if not event.message[0].data["text"]:
del event.message[0] 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] 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[ event.message[0].data["text"] = event.message[0].data[
"text"].lstrip() "text"].lstrip()
if not event.message[0].data["text"]: if not event.message[0].data["text"]:

View File

@ -92,8 +92,9 @@ async def _check_matcher(priority: int, bot: Bot, event: Event,
async def _check(Matcher: Type[Matcher], bot: Bot, event: Event, async def _check(Matcher: Type[Matcher], bot: Bot, event: Event,
state: dict) -> Optional[Type[Matcher]]: state: dict) -> Optional[Type[Matcher]]:
try: try:
if await Matcher.check_perm( if (not Matcher.expire_time or datetime.now() <= Matcher.expire_time
bot, event) and await Matcher.check_rule(bot, event, state): ) and await Matcher.check_perm(
bot, event) and await Matcher.check_rule(bot, event, state):
return Matcher return Matcher
except Exception as e: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
@ -149,6 +150,8 @@ async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event,
try: try:
logger.debug(f"Running matcher {matcher}") logger.debug(f"Running matcher {matcher}")
await matcher.run(bot, event, state) await matcher.run(bot, event, state)
except StopPropagation as e:
exception = e
except Exception as e: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>" f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>"
@ -166,7 +169,7 @@ async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event,
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>" "<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
) )
if matcher.block: if matcher.block or isinstance(exception, StopPropagation):
raise StopPropagation raise StopPropagation

View File

@ -11,6 +11,7 @@ import pkgutil
import importlib import importlib
from dataclasses import dataclass from dataclasses import dataclass
from importlib._bootstrap import _load from importlib._bootstrap import _load
from contextvars import Context, ContextVar, copy_context
from nonebot.log import logger from nonebot.log import logger
from nonebot.matcher import Matcher 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) @dataclass(eq=False)
@ -46,6 +89,11 @@ class Plugin(object):
- **类型**: ``Set[Type[Matcher]]`` - **类型**: ``Set[Type[Matcher]]``
- **说明**: 插件内定义的 ``Matcher`` - **说明**: 插件内定义的 ``Matcher``
""" """
export: Export
"""
- **类型**: ``Export``
- **说明**: 插件内定义的导出内容
"""
def on(type: str = "", def on(type: str = "",
@ -80,7 +128,7 @@ def on(type: str = "",
block=block, block=block,
handlers=handlers, handlers=handlers,
default_state=state) default_state=state)
_tmp_matchers.add(matcher) _tmp_matchers.get().add(matcher)
return matcher return matcher
@ -112,7 +160,7 @@ def on_metaevent(rule: Optional[Union[Rule, RuleChecker]] = None,
block=block, block=block,
handlers=handlers, handlers=handlers,
default_state=state) default_state=state)
_tmp_matchers.add(matcher) _tmp_matchers.get().add(matcher)
return matcher return matcher
@ -146,7 +194,7 @@ def on_message(rule: Optional[Union[Rule, RuleChecker]] = None,
block=block, block=block,
handlers=handlers, handlers=handlers,
default_state=state) default_state=state)
_tmp_matchers.add(matcher) _tmp_matchers.get().add(matcher)
return matcher return matcher
@ -178,7 +226,7 @@ def on_notice(rule: Optional[Union[Rule, RuleChecker]] = None,
block=block, block=block,
handlers=handlers, handlers=handlers,
default_state=state) default_state=state)
_tmp_matchers.add(matcher) _tmp_matchers.get().add(matcher)
return matcher return matcher
@ -210,7 +258,7 @@ def on_request(rule: Optional[Union[Rule, RuleChecker]] = None,
block=block, block=block,
handlers=handlers, handlers=handlers,
default_state=state) default_state=state)
_tmp_matchers.add(matcher) _tmp_matchers.get().add(matcher)
return matcher return matcher
@ -387,27 +435,35 @@ def load_plugin(module_path: str) -> Optional[Plugin]:
:返回: :返回:
- ``Optional[Plugin]`` - ``Optional[Plugin]``
""" """
try:
_tmp_matchers.clear() def _load_plugin(module_path: str) -> Optional[Plugin]:
if module_path in plugins: try:
return plugins[module_path] _tmp_matchers.set(set())
elif module_path in sys.modules: _export.set(Export())
logger.warning( if module_path in plugins:
f"Module {module_path} has been loaded by other plugins! Ignored" 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 "<y>{module_path}</y>"')
return plugin
except Exception as e:
logger.opt(colors=True, exception=e).error(
f'<r><bg #f8bbd0>Failed to import "{module_path}"</bg #f8bbd0></r>'
) )
return return None
module = importlib.import_module(module_path)
for m in _tmp_matchers: context: Context = copy_context()
m.module = module_path return context.run(_load_plugin, module_path)
plugin = Plugin(module_path, module, _tmp_matchers.copy())
plugins[module_path] = plugin
logger.opt(
colors=True).info(f'Succeeded to import "<y>{module_path}</y>"')
return plugin
except Exception as e:
logger.opt(colors=True, exception=e).error(
f'<r><bg #f8bbd0>Failed to import "{module_path}"</bg #f8bbd0></r>')
return None
def load_plugins(*plugin_dir: str) -> Set[Plugin]: def load_plugins(*plugin_dir: str) -> Set[Plugin]:
@ -419,33 +475,42 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
:返回: :返回:
- ``Set[Plugin]`` - ``Set[Plugin]``
""" """
loaded_plugins = set()
for module_info in pkgutil.iter_modules(plugin_dir): def _load_plugin(module_info) -> Optional[Plugin]:
_tmp_matchers.clear() _tmp_matchers.set(set())
_export.set(Export())
name = module_info.name name = module_info.name
if name.startswith("_"): if name.startswith("_"):
continue return
spec = module_info.module_finder.find_spec(name, None) spec = module_info.module_finder.find_spec(name, None)
if spec.name in plugins: if spec.name in plugins:
continue return
elif spec.name in sys.modules: elif spec.name in sys.modules:
logger.warning( logger.warning(
f"Module {spec.name} has been loaded by other plugin! Ignored") f"Module {spec.name} has been loaded by other plugin! Ignored")
continue return
try: try:
module = _load(spec) module = _load(spec)
for m in _tmp_matchers: for m in _tmp_matchers.get():
m.module = name m.module = name
plugin = Plugin(name, module, _tmp_matchers.copy()) plugin = Plugin(name, module, _tmp_matchers.get(), _export.get())
plugins[name] = plugin plugins[name] = plugin
loaded_plugins.add(plugin)
logger.opt(colors=True).info(f'Succeeded to import "<y>{name}</y>"') logger.opt(colors=True).info(f'Succeeded to import "<y>{name}</y>"')
return plugin
except Exception as e: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
f'<r><bg #f8bbd0>Failed to import "{name}"</bg #f8bbd0></r>') f'<r><bg #f8bbd0>Failed to import "{name}"</bg #f8bbd0></r>')
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 return loaded_plugins
@ -459,11 +524,46 @@ def load_builtin_plugins() -> Optional[Plugin]:
return load_plugin("nonebot.plugins.base") 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]: def get_loaded_plugins() -> Set[Plugin]:
""" """
:说明: :说明:
获取当前已导入的插件 获取当前已导入的所有插件
:返回: :返回:
- ``Set[Plugin]`` - ``Set[Plugin]``
""" """
return set(plugins.values()) 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

View File

@ -1,17 +1,32 @@
import re import re
from contextvars import ContextVar
from nonebot.typing import Rule, Matcher, Handler, Permission, RuleChecker from nonebot.typing import Rule, Matcher, Handler, Permission, RuleChecker
from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType
plugins: Dict[str, "Plugin"] = ... 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): class Plugin(object):
name: str name: str
module: ModuleType module: ModuleType
matcher: Set[Type[Matcher]] matcher: Set[Type[Matcher]]
export: Export
def on(type: str = ..., 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 get_loaded_plugins() -> Set[Plugin]:
... ...
def export() -> Export:
...
def require(name: str) -> Export:
...
class CommandGroup: class CommandGroup:
def __init__(self, def __init__(self,

View File

@ -4,6 +4,14 @@ sidebar: auto
# 更新日志 # 更新日志
## v2.0.0a7
- 修复 cqhttp 检查 to me 时出现 IndexError
- 修复已失效的事件响应器仍会运行一次的 bug
- 修改 cqhttp 检查 reply 时未去除后续 at 以及空格
- 添加 get_plugin 获取插件函数
- 添加插件 export, require 方法
## v2.0.0a6 ## v2.0.0a6
- 修复 block 失效问题 (hotfix) - 修复 block 失效问题 (hotfix)

View File

@ -22,6 +22,8 @@ nonebot.load_builtin_plugins()
# load local plugins # load local plugins
nonebot.load_plugins("test_plugins") nonebot.load_plugins("test_plugins")
print(nonebot.require("test_export"))
# modify some config / config depends on loaded configs # modify some config / config depends on loaded configs
config = nonebot.get_driver().config config = nonebot.get_driver().config
config.custom_config3 = config.custom_config1 config.custom_config3 = config.custom_config1

View File

@ -0,0 +1,15 @@
import nonebot
export = nonebot.export()
export.foo = "bar"
export["bar"] = "foo"
@export
def a():
pass
@export.sub
def b():
pass