Files
WechatHookBot/utils/plugin_base.py

233 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
插件基类模块
提供插件的基础功能:
- 生命周期钩子on_load, on_enable, on_disable, on_unload, on_reload
- 定时任务管理
- 依赖声明
- 插件元数据
"""
from abc import ABC
from enum import Enum
from typing import Any, Dict, List, Optional, TYPE_CHECKING
from loguru import logger
from .decorators import scheduler, add_job_safe, remove_job_safe
if TYPE_CHECKING:
from utils.plugin_manager import PluginManager
class PluginState(Enum):
"""插件状态"""
UNLOADED = "unloaded" # 未加载
LOADED = "loaded" # 已加载(未启用)
ENABLED = "enabled" # 已启用
DISABLED = "disabled" # 已禁用
ERROR = "error" # 错误状态
class PluginBase(ABC):
"""
插件基类
生命周期:
1. __init__() - 构造函数(同步)
2. on_load() - 加载时调用(异步,可访问其他插件)
3. async_init() - 异步初始化(异步,加载配置、资源等)
4. on_enable() - 启用时调用(异步,注册定时任务)
5. on_disable() - 禁用时调用(异步,清理定时任务)
6. on_unload() - 卸载时调用(异步,释放资源)
7. on_reload() - 重载前调用(异步,保存状态)
使用示例:
class MyPlugin(PluginBase):
description = "我的插件"
author = "作者"
version = "1.0.0"
dependencies = ["AIChat"] # 依赖的插件
load_priority = 60 # 加载优先级
async def on_load(self, plugin_manager):
# 可以访问其他插件
aichat = plugin_manager.plugins.get("AIChat")
async def async_init(self):
# 加载配置、初始化资源
self.config = load_config()
async def on_enable(self, bot):
await super().on_enable(bot) # 注册定时任务
# 额外的启用逻辑
async def on_disable(self):
await super().on_disable() # 清理定时任务
# 额外的禁用逻辑
async def on_unload(self):
# 释放资源、关闭连接
await self.close_connections()
async def on_reload(self) -> dict:
# 返回需要保存的状态
return {"counter": self.counter}
async def restore_state(self, state: dict):
# 重载后恢复状态
self.counter = state.get("counter", 0)
"""
# ==================== 插件元数据 ====================
description: str = "暂无描述"
author: str = "未知"
version: str = "1.0.0"
# 插件依赖(填写依赖的插件类名列表)
dependencies: List[str] = []
# 加载优先级数值越大越先加载默认50
load_priority: int = 50
# ==================== 实例属性 ====================
def __init__(self):
self.enabled = False
self.state = PluginState.UNLOADED
self._scheduled_jobs: set = set()
self._bot = None
self._plugin_manager: Optional["PluginManager"] = None
self._saved_state: Dict[str, Any] = {}
# ==================== 生命周期钩子 ====================
async def on_load(self, plugin_manager: "PluginManager"):
"""
插件加载时调用
此时其他依赖的插件已经加载完成,可以安全访问。
Args:
plugin_manager: 插件管理器实例
"""
self._plugin_manager = plugin_manager
self.state = PluginState.LOADED
logger.debug(f"[{self.__class__.__name__}] on_load 调用")
async def async_init(self):
"""
插件异步初始化
用于加载配置、初始化资源等耗时操作。
在 on_load 之后、on_enable 之前调用。
"""
pass
async def on_enable(self, bot=None):
"""
插件启用时调用
注册定时任务、启动后台服务等。
Args:
bot: WechatHookClient 实例
"""
self._bot = bot
self.enabled = True
self.state = PluginState.ENABLED
# 注册定时任务
for method_name in dir(self):
method = getattr(self, method_name)
if hasattr(method, '_is_scheduled'):
job_id = getattr(method, '_job_id')
trigger = getattr(method, '_schedule_trigger')
trigger_args = getattr(method, '_schedule_args')
add_job_safe(scheduler, job_id, method, bot, trigger, **trigger_args)
self._scheduled_jobs.add(job_id)
if self._scheduled_jobs:
logger.success(f"插件 {self.__class__.__name__} 已加载定时任务: {self._scheduled_jobs}")
async def on_disable(self):
"""
插件禁用时调用
清理定时任务、停止后台服务等。
"""
self.enabled = False
self.state = PluginState.DISABLED
# 移除定时任务
for job_id in self._scheduled_jobs:
remove_job_safe(scheduler, job_id)
if self._scheduled_jobs:
logger.info(f"已卸载定时任务: {self._scheduled_jobs}")
self._scheduled_jobs.clear()
async def on_unload(self):
"""
插件卸载时调用
释放资源、关闭连接、保存数据等。
在 on_disable 之后调用。
"""
self.state = PluginState.UNLOADED
self._bot = None
self._plugin_manager = None
logger.debug(f"[{self.__class__.__name__}] on_unload 调用")
async def on_reload(self) -> Dict[str, Any]:
"""
插件重载前调用
返回需要在重载后恢复的状态数据。
Returns:
需要保存的状态字典
"""
logger.debug(f"[{self.__class__.__name__}] on_reload 调用")
return {}
async def restore_state(self, state: Dict[str, Any]):
"""
重载后恢复状态
Args:
state: on_reload 返回的状态字典
"""
self._saved_state = state
logger.debug(f"[{self.__class__.__name__}] 状态已恢复: {list(state.keys())}")
# ==================== 辅助方法 ====================
def get_plugin(self, plugin_name: str) -> Optional["PluginBase"]:
"""
获取其他插件实例
Args:
plugin_name: 插件类名
Returns:
插件实例,不存在返回 None
"""
if self._plugin_manager:
return self._plugin_manager.plugins.get(plugin_name)
return None
def get_bot(self):
"""获取 bot 实例"""
return self._bot
@property
def plugin_name(self) -> str:
"""获取插件名称"""
return self.__class__.__name__
def __repr__(self) -> str:
return f"<{self.__class__.__name__} v{self.version} state={self.state.value}>"