"""iamai plugins
This module contains the base class for all iamai plugins.
"""
import inspect
from abc import ABC, abstractmethod
from enum import Enum
from typing import (
TYPE_CHECKING,
Any,
ClassVar,
Generic,
NoReturn,
Optional,
Tuple,
Type,
cast,
final,
)
from typing_extensions import Annotated, get_args, get_origin
from iamai.config import ConfigModel
from iamai.dependencies import Depends
from iamai.event import Event
from iamai.exceptions import SkipException, StopException
from iamai.typing import ConfigT, EventT, StateT
from iamai.utils import is_config_class
if TYPE_CHECKING:
from iamai.bot import Bot
__all__ = ["Plugin", "PluginLoadType"]
[docs]
class PluginLoadType(Enum):
"""Plugins loaded types."""
DIR = "dir"
NAME = "name"
FILE = "file"
CLASS = "class"
[docs]
class Plugin(ABC, Generic[EventT, StateT, ConfigT]):
"""Base class for all iamai plugins.
Attributes:
event: The event currently being processed by this plugin.
priority: The priority of the plugin. The smaller the number, the higher the priority. The default is 0.
block: Whether to prevent the propagation of events after the plug-in is executed. ``True`` means blocking.
__plugin_load_type__: Plugin load type, automatically set by iamai, reflects how this plugin is loaded.
__plugin_file_path__: ``None`` when the plugin loading type is ``PluginLoadType.CLASS``,
Otherwise, the location of the Python module in which the plugin is defined.
"""
priority: ClassVar[int] = 0
block: ClassVar[bool] = False
# Cannot use ClassVar because PEP 526 does not allow it
Config: Type[ConfigT]
__plugin_load_type__: ClassVar[PluginLoadType]
__plugin_file_path__: ClassVar[Optional[str]]
if TYPE_CHECKING:
event: EventT
else:
event = Depends(Event)
def __init_state__(self) -> Optional[StateT]:
"""Initialize plugin state."""
def __init_subclass__(
cls,
config: Optional[Type[ConfigT]] = None,
init_state: Optional[StateT] = None,
**_kwargs: Any,
) -> None:
"""Initialize subclasses.
Args:
config: Configuration class.
init_state: initial state.
"""
super().__init_subclass__()
orig_bases: Tuple[type, ...] = getattr(cls, "__orig_bases__", ())
for orig_base in orig_bases:
origin_class = get_origin(orig_base)
if inspect.isclass(origin_class) and issubclass(origin_class, Plugin):
try:
_event_t, state_t, config_t = cast(
Tuple[EventT, StateT, ConfigT], get_args(orig_base)
)
except ValueError: # pragma: no cover
continue
if (
config is None
and inspect.isclass(config_t)
and issubclass(config_t, ConfigModel)
):
config = config_t # pyright: ignore
if (
init_state is None
and get_origin(state_t) is Annotated
and hasattr(state_t, "__metadata__")
):
init_state = state_t.__metadata__[0] # pyright: ignore
if not hasattr(cls, "Config") and config is not None:
cls.Config = config
if cls.__init_state__ is Plugin.__init_state__ and init_state is not None:
cls.__init_state__ = lambda _: init_state # type: ignore
@final
@property
def name(self) -> str:
"""plugin class name."""
return self.__class__.__name__
@final
@property
def bot(self) -> "Bot":
"""bot object."""
return self.event.adapter.bot # pylint: disable=no-member
@final
@property
def config(self) -> ConfigT:
"""plugin configuration."""
default: Any = None
config_class = getattr(self, "Config", None)
if is_config_class(config_class):
return getattr(
self.bot.config.plugin,
config_class.__config_name__,
default,
)
return default
[docs]
@final
def stop(self) -> NoReturn:
"""Stop propagation of current events."""
raise StopException
[docs]
@final
def skip(self) -> NoReturn:
"""Skips itself and continues propagation of the current event."""
raise SkipException
@property
def state(self) -> StateT:
"""plugin status."""
return self.bot.plugin_state[self.name]
@state.setter
@final
def state(self, value: StateT) -> None:
self.bot.plugin_state[self.name] = value
[docs]
@abstractmethod
async def handle(self) -> None:
"""Method to handle events. iamai will call this method when the ``rule()`` method returns ``True``. Each plugin must implement this method."""
raise NotImplementedError
[docs]
@abstractmethod
async def rule(self) -> bool:
"""Method to match the event. When the event is processed, this method will be called in sequence according to the priority of the plugin. When this method returns ``True``, the event will be handed over to this plugin for processing. Each plugin must implement this method.
.. note::
It is not recommended to implement event processing directly in this method. Please leave the specific processing of events to the ``handle()`` method.
"""
raise NotImplementedError