2019-01-03 19:58:56 +08:00
|
|
|
import os
|
|
|
|
import re
|
2020-04-07 21:58:10 +08:00
|
|
|
import warnings
|
|
|
|
import importlib
|
|
|
|
from types import ModuleType
|
|
|
|
from typing import Any, Set, Dict, Optional
|
2019-01-03 19:58:56 +08:00
|
|
|
|
|
|
|
from .log import logger
|
2020-04-07 21:58:10 +08:00
|
|
|
from .command import Command, CommandManager
|
|
|
|
from .natural_language import NLProcessor, NLPManager
|
|
|
|
from .notice_request import _bus, EventHandler
|
2019-01-03 19:58:56 +08:00
|
|
|
|
2019-01-03 20:03:14 +08:00
|
|
|
|
2019-01-09 21:29:10 +08:00
|
|
|
class Plugin:
|
2020-04-07 21:58:10 +08:00
|
|
|
__slots__ = ('module', 'name', 'usage', 'commands', 'nl_processors', 'event_handlers')
|
2019-01-09 21:29:10 +08:00
|
|
|
|
2020-04-07 21:58:10 +08:00
|
|
|
def __init__(self, module: ModuleType,
|
2019-01-09 21:29:10 +08:00
|
|
|
name: Optional[str] = None,
|
2020-04-07 21:58:10 +08:00
|
|
|
usage: Optional[Any] = None,
|
|
|
|
commands: Set[Command] = set(),
|
|
|
|
nl_processors: Set[NLProcessor] = set(),
|
|
|
|
event_handlers: Set[EventHandler] = set()):
|
2019-01-09 21:29:10 +08:00
|
|
|
self.module = module
|
|
|
|
self.name = name
|
|
|
|
self.usage = usage
|
2020-04-07 21:58:10 +08:00
|
|
|
self.commands = commands
|
|
|
|
self.nl_processors = nl_processors
|
|
|
|
self.event_handlers = event_handlers
|
|
|
|
|
|
|
|
class PluginManager:
|
|
|
|
_plugins: Dict[str, Plugin] = {}
|
|
|
|
_anonymous_plugins: Set[Plugin] = set()
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.cmd_manager = CommandManager()
|
|
|
|
self.nlp_manager = NLPManager()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def add_plugin(cls, plugin: Plugin) -> None:
|
|
|
|
"""Register a plugin
|
|
|
|
|
|
|
|
Args:
|
|
|
|
plugin (Plugin): Plugin object
|
|
|
|
"""
|
|
|
|
if plugin.name:
|
|
|
|
if plugin.name in cls._plugins:
|
|
|
|
warnings.warn(f"Plugin {plugin.name} already exists")
|
|
|
|
return
|
|
|
|
cls._plugins[plugin.name] = plugin
|
|
|
|
else:
|
|
|
|
cls._anonymous_plugins.add(plugin)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_plugin(cls, name: str) -> Optional[Plugin]:
|
|
|
|
return cls._plugins.get(name)
|
|
|
|
|
|
|
|
# TODO: plugin重加载
|
|
|
|
@classmethod
|
|
|
|
def reload_plugin(cls, plugin: Plugin) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def switch_plugin_global(cls, name: str, state: Optional[bool] = None) -> None:
|
|
|
|
"""Change plugin state globally or simply switch it if `state` is None
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name (str): Plugin name
|
|
|
|
state (Optional[bool]): State to change to. Defaults to None.
|
|
|
|
"""
|
|
|
|
plugin = cls.get_plugin(name)
|
|
|
|
if not plugin:
|
|
|
|
warnings.warn(f"Plugin {name} not found")
|
|
|
|
return
|
|
|
|
for command in plugin.commands:
|
|
|
|
CommandManager.switch_command_global(command.name, state)
|
|
|
|
for nl_processor in plugin.nl_processors:
|
2020-04-11 14:56:39 +08:00
|
|
|
NLPManager.switch_nlprocessor_global(nl_processor, state)
|
|
|
|
for event_handler in plugin.event_handlers:
|
|
|
|
for event in event_handler.events:
|
|
|
|
if event_handler.func in _bus._subscribers[event] and not state:
|
|
|
|
_bus.unsubscribe(event, event_handler.func)
|
|
|
|
elif event_handler.func not in _bus._subscribers[event] and state != False:
|
|
|
|
_bus.subscribe(event, event_handler.func)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def switch_command_global(cls, name: str, state: Optional[bool] = None) -> None:
|
|
|
|
"""Change plugin command state globally or simply switch it if `state` is None
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name (str): Plugin name
|
|
|
|
state (Optional[bool]): State to change to. Defaults to None.
|
|
|
|
"""
|
|
|
|
plugin = cls.get_plugin(name)
|
|
|
|
if not plugin:
|
|
|
|
warnings.warn(f"Plugin {name} not found")
|
|
|
|
return
|
|
|
|
for command in plugin.commands:
|
|
|
|
CommandManager.switch_command_global(command.name, state)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def switch_nlprocessor_global(cls, name: str, state: Optional[bool] = None) -> None:
|
|
|
|
"""Change plugin nlprocessor state globally or simply switch it if `state` is None
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name (str): Plugin name
|
|
|
|
state (Optional[bool]): State to change to. Defaults to None.
|
|
|
|
"""
|
|
|
|
plugin = cls.get_plugin(name)
|
|
|
|
if not plugin:
|
|
|
|
warnings.warn(f"Plugin {name} not found")
|
|
|
|
return
|
|
|
|
for processor in plugin.nl_processors:
|
|
|
|
NLPManager.switch_nlprocessor_global(processor, state)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def switch_eventhandler_global(cls, name: str, state: Optional[bool] = None) -> None:
|
|
|
|
"""Change plugin event handler state globally or simply switch it if `state` is None
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name (str): Plugin name
|
|
|
|
state (Optional[bool]): State to change to. Defaults to None.
|
|
|
|
"""
|
|
|
|
plugin = cls.get_plugin(name)
|
|
|
|
if not plugin:
|
|
|
|
warnings.warn(f"Plugin {name} not found")
|
|
|
|
return
|
2020-04-07 21:58:10 +08:00
|
|
|
for event_handler in plugin.event_handlers:
|
|
|
|
for event in event_handler.events:
|
|
|
|
if event_handler.func in _bus._subscribers[event] and not state:
|
|
|
|
_bus.unsubscribe(event, event_handler.func)
|
|
|
|
elif event_handler.func not in _bus._subscribers[event] and state != False:
|
|
|
|
_bus.subscribe(event, event_handler.func)
|
|
|
|
|
|
|
|
def switch_plugin(self, name: str, state: Optional[bool] = None) -> None:
|
|
|
|
"""Change plugin state or simply switch it if `state` is None
|
|
|
|
|
2020-04-11 14:56:39 +08:00
|
|
|
Tips:
|
|
|
|
This method will only change the state of the plugin's
|
|
|
|
commands and natural language processors since change
|
|
|
|
state of the event handler partially is meaningless.
|
|
|
|
|
2020-04-07 21:58:10 +08:00
|
|
|
Args:
|
|
|
|
name (str): Plugin name
|
|
|
|
state (Optional[bool]): State to change to. Defaults to None.
|
|
|
|
"""
|
|
|
|
plugin = self.get_plugin(name)
|
|
|
|
if not plugin:
|
|
|
|
warnings.warn(f"Plugin {name} not found")
|
|
|
|
return
|
|
|
|
for command in plugin.commands:
|
|
|
|
self.cmd_manager.switch_command(command.name, state)
|
|
|
|
for nl_processor in plugin.nl_processors:
|
2020-04-11 14:56:39 +08:00
|
|
|
self.nlp_manager.switch_nlprocessor(nl_processor, state)
|
|
|
|
|
|
|
|
def switch_command(self, name: str, state: Optional[bool] = None) -> None:
|
|
|
|
"""Change plugin command state or simply switch it if `state` is None
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name (str): Plugin name
|
|
|
|
state (Optional[bool]): State to change to. Defaults to None.
|
|
|
|
"""
|
|
|
|
plugin = self.get_plugin(name)
|
|
|
|
if not plugin:
|
|
|
|
warnings.warn(f"Plugin {name} not found")
|
|
|
|
return
|
|
|
|
for command in plugin.commands:
|
|
|
|
self.cmd_manager.switch_command(command.name, state)
|
|
|
|
|
|
|
|
def switch_nlprocessor(self, name: str, state: Optional[bool] = None) -> None:
|
|
|
|
"""Change plugin nlprocessor state or simply switch it if `state` is None
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name (str): Plugin name
|
|
|
|
state (Optional[bool]): State to change to. Defaults to None.
|
|
|
|
"""
|
|
|
|
plugin = self.get_plugin(name)
|
|
|
|
if not plugin:
|
|
|
|
warnings.warn(f"Plugin {name} not found")
|
|
|
|
return
|
|
|
|
for processor in plugin.nl_processors:
|
|
|
|
self.nlp_manager.switch_nlprocessor(processor, state)
|
2020-04-07 21:58:10 +08:00
|
|
|
|
|
|
|
|
|
|
|
def load_plugin(module_name: str) -> Optional[Plugin]:
|
2019-01-03 19:58:56 +08:00
|
|
|
"""
|
|
|
|
Load a module as a plugin.
|
|
|
|
|
|
|
|
:param module_name: name of module to import
|
|
|
|
:return: successful or not
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
module = importlib.import_module(module_name)
|
|
|
|
name = getattr(module, '__plugin_name__', None)
|
|
|
|
usage = getattr(module, '__plugin_usage__', None)
|
2020-04-07 21:58:10 +08:00
|
|
|
commands = set()
|
|
|
|
nl_processors = set()
|
|
|
|
event_handlers = set()
|
|
|
|
for attr in dir(module):
|
|
|
|
func = getattr(module, attr)
|
|
|
|
if isinstance(func, Command):
|
|
|
|
commands.add(func)
|
|
|
|
elif isinstance(func, NLProcessor):
|
|
|
|
nl_processors.add(func)
|
|
|
|
elif isinstance(func, EventHandler):
|
|
|
|
event_handlers.add(func)
|
|
|
|
plugin = Plugin(module, name, usage, commands, nl_processors, event_handlers)
|
|
|
|
PluginManager.add_plugin(plugin)
|
2019-01-03 19:58:56 +08:00
|
|
|
logger.info(f'Succeeded to import "{module_name}"')
|
2020-04-07 21:58:10 +08:00
|
|
|
return plugin
|
2019-01-03 19:58:56 +08:00
|
|
|
except Exception as e:
|
|
|
|
logger.error(f'Failed to import "{module_name}", error: {e}')
|
|
|
|
logger.exception(e)
|
2020-04-07 21:58:10 +08:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# TODO: plugin重加载
|
|
|
|
def reload_plugin(module_name: str) -> Optional[Plugin]:
|
|
|
|
pass
|
2019-01-03 19:58:56 +08:00
|
|
|
|
|
|
|
|
2020-04-07 21:58:10 +08:00
|
|
|
def load_plugins(plugin_dir: str, module_prefix: str) -> Set[Plugin]:
|
2019-01-03 19:58:56 +08:00
|
|
|
"""
|
|
|
|
Find all non-hidden modules or packages in a given directory,
|
|
|
|
and import them with the given module prefix.
|
|
|
|
|
|
|
|
:param plugin_dir: plugin directory to search
|
|
|
|
:param module_prefix: module prefix used while importing
|
|
|
|
:return: number of plugins successfully loaded
|
|
|
|
"""
|
2020-04-07 21:58:10 +08:00
|
|
|
count = set()
|
2019-01-03 19:58:56 +08:00
|
|
|
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
|
|
|
|
|
2020-04-07 21:58:10 +08:00
|
|
|
result = load_plugin(f'{module_prefix}.{m.group(1)}')
|
|
|
|
if result:
|
|
|
|
count.add(result)
|
2019-01-03 19:58:56 +08:00
|
|
|
return count
|
|
|
|
|
|
|
|
|
2020-04-07 21:58:10 +08:00
|
|
|
def load_builtin_plugins() -> Set[Plugin]:
|
2019-01-03 19:58:56 +08:00
|
|
|
"""
|
|
|
|
Load built-in plugins distributed along with "nonebot" package.
|
|
|
|
"""
|
|
|
|
plugin_dir = os.path.join(os.path.dirname(__file__), 'plugins')
|
|
|
|
return load_plugins(plugin_dir, 'nonebot.plugins')
|
|
|
|
|
|
|
|
|
|
|
|
def get_loaded_plugins() -> Set[Plugin]:
|
2019-01-03 20:04:31 +08:00
|
|
|
"""
|
|
|
|
Get all plugins loaded.
|
|
|
|
|
|
|
|
:return: a set of Plugin objects
|
|
|
|
"""
|
2020-04-07 21:58:10 +08:00
|
|
|
return set(PluginManager._plugins.values()) | PluginManager._anonymous_plugins
|