mirror of
https://github.com/nonebot/nonebot2.git
synced 2024-11-24 09:05:04 +08:00
plugin load
This commit is contained in:
parent
16099b2c35
commit
7529559d24
9
nonebot/__init__.py
Normal file
9
nonebot/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from .log import logger
|
||||||
|
|
||||||
|
logger.setLevel(level=logging.DEBUG)
|
||||||
|
|
||||||
|
from .plugin import load_plugins, get_loaded_plugins
|
@ -1,4 +1,7 @@
|
|||||||
from typing import Dict, Any, Optional
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
|
||||||
class Event(dict):
|
class Event(dict):
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
class PausedException(Exception):
|
class PausedException(Exception):
|
||||||
"""Block a message from further handling and try to receive a new message"""
|
"""Block a message from further handling and try to receive a new message"""
|
||||||
pass
|
pass
|
||||||
|
11
nonebot/log.py
Normal file
11
nonebot/log.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
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)
|
@ -1,198 +1,149 @@
|
|||||||
import re
|
#!/usr/bin/env python3
|
||||||
import copy
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Type, Union, Optional, Callable
|
from collections import defaultdict
|
||||||
|
from typing import Type, List, Dict, Optional, Callable
|
||||||
|
|
||||||
from .event import Event
|
from .event import Event
|
||||||
from .typing import Scope, Handler
|
from .typing import Handler
|
||||||
from .rule import Rule, startswith, regex, user
|
from .rule import Rule, user
|
||||||
from .exception import PausedException, RejectedException, FinishedException
|
from .exception import PausedException, RejectedException, FinishedException
|
||||||
|
|
||||||
|
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
|
||||||
|
|
||||||
|
|
||||||
class Matcher:
|
class Matcher:
|
||||||
|
|
||||||
rule: Rule = Rule()
|
rule: Rule = Rule()
|
||||||
scope: Scope = "ALL"
|
handlers: List[Handler] = []
|
||||||
permission: str = "ALL"
|
|
||||||
block: bool = True
|
|
||||||
handlers: list = []
|
|
||||||
temp: bool = False
|
temp: bool = False
|
||||||
|
priority: int = 1
|
||||||
|
|
||||||
_default_state: dict = {}
|
_default_state: dict = {}
|
||||||
_default_parser: Optional[Callable[[Event, dict], None]] = None
|
|
||||||
_args_parser: Optional[Callable[[Event, dict], None]] = None
|
# _default_parser: Optional[Callable[[Event, dict], None]] = None
|
||||||
|
# _args_parser: Optional[Callable[[Event, dict], None]] = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.handlers = self.handlers.copy()
|
self.handlers = self.handlers.copy()
|
||||||
self.state = self._default_state.copy()
|
self.state = self._default_state.copy()
|
||||||
self.parser = self._args_parser or self._default_parser
|
# self.parser = self._args_parser or self._default_parser
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(
|
def new(cls,
|
||||||
cls,
|
|
||||||
rule: Rule = Rule(),
|
rule: Rule = Rule(),
|
||||||
scope: Scope = "ALL",
|
|
||||||
permission: str = "ALL",
|
|
||||||
block: bool = True,
|
|
||||||
handlers: list = [],
|
handlers: list = [],
|
||||||
temp: bool = False,
|
temp: bool = False,
|
||||||
|
priority: int = 1,
|
||||||
*,
|
*,
|
||||||
default_state: dict = {},
|
default_state: dict = {}) -> Type["Matcher"]:
|
||||||
default_parser: Optional[Callable[[Event, dict], None]] = None,
|
|
||||||
args_parser: Optional[Callable[[Event, dict], None]] = None
|
|
||||||
) -> Type["Matcher"]:
|
|
||||||
|
|
||||||
# class NewMatcher(cls):
|
|
||||||
# rule: Rule = rule
|
|
||||||
# scope: Scope = scope
|
|
||||||
# permission: str = permission
|
|
||||||
# block: bool = block
|
|
||||||
# handlers: list = handlers
|
|
||||||
# temp: bool = temp
|
|
||||||
|
|
||||||
# _default_state = default_state
|
|
||||||
|
|
||||||
NewMatcher = type(
|
NewMatcher = type(
|
||||||
"Matcher", (Matcher,), {
|
"Matcher", (Matcher,), {
|
||||||
"rule": rule,
|
"rule": rule,
|
||||||
"scope": scope,
|
|
||||||
"permission": permission,
|
|
||||||
"block": block,
|
|
||||||
"handlers": handlers,
|
"handlers": handlers,
|
||||||
"temp": temp,
|
"temp": temp,
|
||||||
"_default_state": default_state,
|
"priority": priority,
|
||||||
"_default_parser": default_parser,
|
"_default_state": default_state
|
||||||
"_args_parser": args_parser,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
matchers[priority].append(NewMatcher)
|
||||||
|
|
||||||
return NewMatcher
|
return NewMatcher
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def args_parser(cls, func: Callable[[Event, dict], None]):
|
||||||
|
# cls._default_parser = func
|
||||||
|
# return func
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def args_parser(cls, func: Callable[[Event, dict], None]):
|
def handle(cls):
|
||||||
cls._default_parser = func
|
|
||||||
|
def _decorator(func: Handler) -> Handler:
|
||||||
|
cls.handlers.append(func)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
return _decorator
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def receive(cls):
|
def receive(cls):
|
||||||
|
|
||||||
def _decorator(func: Handler) -> Handler:
|
def _decorator(func: Handler) -> Handler:
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def _handler(event: Event, state: dict):
|
async def _handler(bot, event: Event, state: dict):
|
||||||
raise PausedException
|
raise PausedException
|
||||||
|
|
||||||
cls.handlers.append(_handler)
|
cls.handlers.append(_handler)
|
||||||
|
cls.handlers.append(func)
|
||||||
|
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return _decorator
|
return _decorator
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def got(cls,
|
# def got(cls,
|
||||||
key: str,
|
# key: str,
|
||||||
prompt: Optional[str] = None,
|
# prompt: Optional[str] = None,
|
||||||
args_parser: Optional[Callable[[Event, dict], None]] = None):
|
# args_parser: Optional[Callable[[Event, dict], None]] = None):
|
||||||
|
|
||||||
def _decorator(func: Handler) -> Handler:
|
# def _decorator(func: Handler) -> Handler:
|
||||||
|
|
||||||
@wraps(func)
|
# @wraps(func)
|
||||||
def _handler(event: Event, state: dict):
|
# def _handler(event: Event, state: dict):
|
||||||
if key not in state:
|
# if key not in state:
|
||||||
if state.get("__current_arg__", None) == key:
|
# if state.get("__current_arg__", None) == key:
|
||||||
state[key] = event.message
|
# state[key] = event.message
|
||||||
del state["__current_arg__"]
|
# del state["__current_arg__"]
|
||||||
return func(event, state)
|
# return func(event, state)
|
||||||
state["__current_arg__"] = key
|
# state["__current_arg__"] = key
|
||||||
cls._args_parser = args_parser
|
# cls._args_parser = args_parser
|
||||||
raise RejectedException
|
# raise RejectedException
|
||||||
|
|
||||||
return func(event, state)
|
# return func(event, state)
|
||||||
|
|
||||||
cls.handlers.append(_handler)
|
# cls.handlers.append(_handler)
|
||||||
|
|
||||||
return func
|
# return func
|
||||||
|
|
||||||
return _decorator
|
# return _decorator
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def finish(cls, prompt: Optional[str] = None):
|
# def finish(cls, prompt: Optional[str] = None):
|
||||||
raise FinishedException
|
# raise FinishedException
|
||||||
|
|
||||||
@classmethod
|
# @classmethod
|
||||||
def reject(cls, prompt: Optional[str] = None):
|
# def reject(cls, prompt: Optional[str] = None):
|
||||||
raise RejectedException
|
# raise RejectedException
|
||||||
|
|
||||||
async def run(self, event):
|
async def run(self, bot, event):
|
||||||
if not self.rule(event):
|
if not self.rule(event):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.parser:
|
# if self.parser:
|
||||||
await self.parser(event, state) # type: ignore
|
# await self.parser(event, state) # type: ignore
|
||||||
|
|
||||||
for _ in range(len(self.handlers)):
|
for _ in range(len(self.handlers)):
|
||||||
handler = self.handlers.pop(0)
|
handler = self.handlers.pop(0)
|
||||||
await handler(event, self.state)
|
await handler(bot, event, self.state)
|
||||||
except RejectedException:
|
except RejectedException:
|
||||||
# TODO: add tmp matcher to matcher tree
|
self.handlers.insert(0, handler) # type: ignore
|
||||||
self.handlers.insert(handler, 0)
|
matcher = Matcher.new(user(event.user_id) & self.rule,
|
||||||
matcher = Matcher.new(self.rule,
|
|
||||||
self.scope,
|
|
||||||
self.permission,
|
|
||||||
self.block,
|
|
||||||
self.handlers,
|
self.handlers,
|
||||||
temp=True,
|
temp=True,
|
||||||
default_state=self.state,
|
priority=0,
|
||||||
default_parser=self._default_parser,
|
default_state=self.state)
|
||||||
args_parser=self._args_parser)
|
matchers[0].append(matcher)
|
||||||
return
|
return
|
||||||
except PausedException:
|
except PausedException:
|
||||||
# TODO: add tmp matcher to matcher tree
|
matcher = Matcher.new(user(event.user_id) & self.rule,
|
||||||
matcher = Matcher.new(self.rule,
|
|
||||||
self.scope,
|
|
||||||
self.permission,
|
|
||||||
self.block,
|
|
||||||
self.handlers,
|
self.handlers,
|
||||||
temp=True,
|
temp=True,
|
||||||
default_state=self.state,
|
priority=0,
|
||||||
default_parser=self._default_parser,
|
default_state=self.state)
|
||||||
args_parser=self._args_parser)
|
matchers[0].append(matcher)
|
||||||
return
|
return
|
||||||
except FinishedException:
|
except FinishedException:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def on_message(rule: Rule,
|
|
||||||
scope="ALL",
|
|
||||||
permission="ALL",
|
|
||||||
block=True,
|
|
||||||
*,
|
|
||||||
handlers=[],
|
|
||||||
temp=False,
|
|
||||||
state={}) -> Type[Matcher]:
|
|
||||||
# TODO: add matcher to matcher tree
|
|
||||||
return Matcher.new(rule,
|
|
||||||
scope,
|
|
||||||
permission,
|
|
||||||
block,
|
|
||||||
handlers=handlers,
|
|
||||||
temp=temp,
|
|
||||||
default_state=state)
|
|
||||||
|
|
||||||
|
|
||||||
# def on_startswith(msg,
|
|
||||||
# start: int = None,
|
|
||||||
# end: int = None,
|
|
||||||
# rule: Optional[Rule] = None,
|
|
||||||
# **kwargs) -> Type[Matcher]:
|
|
||||||
# return on_message(startswith(msg, start, end) &
|
|
||||||
# rule, **kwargs) if rule else on_message(
|
|
||||||
# startswith(msg, start, end), **kwargs)
|
|
||||||
|
|
||||||
# def on_regex(pattern,
|
|
||||||
# flags: Union[int, re.RegexFlag] = 0,
|
|
||||||
# rule: Optional[Rule] = None,
|
|
||||||
# **kwargs) -> Type[Matcher]:
|
|
||||||
# return on_message(regex(pattern, flags) &
|
|
||||||
# rule, **kwargs) if rule else on_message(
|
|
||||||
# regex(pattern, flags), **kwargs)
|
|
||||||
|
99
nonebot/plugin.py
Normal file
99
nonebot/plugin.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import importlib
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Set, Dict, Type, Optional
|
||||||
|
|
||||||
|
from .log import logger
|
||||||
|
from .rule import Rule
|
||||||
|
from .matcher import Matcher
|
||||||
|
|
||||||
|
plugins: Dict[str, "Plugin"] = {}
|
||||||
|
|
||||||
|
_tmp_matchers: Set[Type[Matcher]] = set()
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(rule: Rule,
|
||||||
|
*,
|
||||||
|
handlers=[],
|
||||||
|
temp=False,
|
||||||
|
priority: int = 1,
|
||||||
|
state={}) -> Type[Matcher]:
|
||||||
|
matcher = Matcher.new(rule,
|
||||||
|
temp=temp,
|
||||||
|
priority=priority,
|
||||||
|
handlers=handlers,
|
||||||
|
default_state=state)
|
||||||
|
_tmp_matchers.add(matcher)
|
||||||
|
return matcher
|
||||||
|
|
||||||
|
|
||||||
|
# def on_startswith(msg,
|
||||||
|
# start: int = None,
|
||||||
|
# end: int = None,
|
||||||
|
# rule: Optional[Rule] = None,
|
||||||
|
# **kwargs) -> Type[Matcher]:
|
||||||
|
# return on_message(startswith(msg, start, end) &
|
||||||
|
# rule, **kwargs) if rule else on_message(
|
||||||
|
# startswith(msg, start, end), **kwargs)
|
||||||
|
|
||||||
|
# def on_regex(pattern,
|
||||||
|
# flags: Union[int, re.RegexFlag] = 0,
|
||||||
|
# rule: Optional[Rule] = None,
|
||||||
|
# **kwargs) -> Type[Matcher]:
|
||||||
|
# return on_message(regex(pattern, flags) &
|
||||||
|
# rule, **kwargs) if rule else on_message(
|
||||||
|
# regex(pattern, flags), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def load_plugin(module_path: str) -> Optional[Plugin]:
|
||||||
|
try:
|
||||||
|
_tmp_matchers.clear()
|
||||||
|
module = importlib.import_module(module_path)
|
||||||
|
plugin = Plugin(module_path, module, _tmp_matchers.copy())
|
||||||
|
plugins[module_path] = plugin
|
||||||
|
logger.info(f"Succeeded to import \"{module_path}\"")
|
||||||
|
return plugin
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to import \"{module_path}\", error: {e}")
|
||||||
|
logger.exception(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_plugins(plugin_dir: str) -> Set[Plugin]:
|
||||||
|
plugins = set()
|
||||||
|
for name in os.listdir(plugin_dir):
|
||||||
|
path = os.path.join(plugin_dir, name)
|
||||||
|
if os.path.isfile(path) and \
|
||||||
|
(name.startswith("_") or not name.endswith(".py")):
|
||||||
|
continue
|
||||||
|
if os.path.isdir(path) and \
|
||||||
|
(name.startswith("_") or not os.path.exists(
|
||||||
|
os.path.join(path, "__init__.py"))):
|
||||||
|
continue
|
||||||
|
|
||||||
|
m = re.match(r"([_A-Z0-9a-z]+)(.py)?", name)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
|
||||||
|
result = load_plugin(f"{plugin_dir.replace(os.sep, '.')}.{m.group(1)}")
|
||||||
|
if result:
|
||||||
|
plugins.add(result)
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
|
def get_loaded_plugins() -> Set[Plugin]:
|
||||||
|
return set(plugins.values())
|
@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import Union, Callable, Optional
|
from typing import Union, Callable, Optional
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
from typing import Literal, Callable
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
Scope = Literal["PRIVATE", "DISCUSS", "GROUP", "ALL"]
|
from typing import Callable, Awaitable
|
||||||
Handler = Callable[["Event", dict], None]
|
|
||||||
|
Handler = Callable[["Bot", "Event", dict], Awaitable[None]]
|
||||||
|
17
tests/bot.py
Normal file
17
tests/bot.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
|
||||||
|
|
||||||
|
import nonebot
|
||||||
|
from nonebot.matcher import matchers
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
nonebot.load_plugins(os.path.join(os.path.dirname(__file__),
|
||||||
|
"test_plugins"))
|
||||||
|
print(nonebot.get_loaded_plugins())
|
||||||
|
print(matchers)
|
||||||
|
print(matchers[1][0].handlers)
|
13
tests/test_plugins/test_matcher.py
Normal file
13
tests/test_plugins/test_matcher.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from nonebot.rule import Rule
|
||||||
|
from nonebot.event import Event
|
||||||
|
from nonebot.plugin import on_message
|
||||||
|
|
||||||
|
test_matcher = on_message(Rule(), state={"default": 1})
|
||||||
|
|
||||||
|
|
||||||
|
@test_matcher.handle()
|
||||||
|
async def test_handler(bot, event: Event, state: dict):
|
||||||
|
print(state)
|
Loading…
Reference in New Issue
Block a user