feat:将旧版示例插件更新,更新mute插件(tts,vtb,doubaopic持续炸裂中)

This commit is contained in:
SengokuCola
2025-06-10 23:36:45 +08:00
parent 9f90b2568c
commit 6455dab5b8
45 changed files with 1657 additions and 2001 deletions

View File

@@ -26,6 +26,34 @@ class ConfigAPI:
"""
return global_config.get(key, default)
def get_config(self, key: str, default: Any = None) -> Any:
"""
从插件配置中获取值,支持嵌套键访问
Args:
key: 配置键名,支持嵌套访问如 "section.subsection.key"
default: 如果配置不存在时返回的默认值
Returns:
Any: 配置值或默认值
"""
# 获取插件配置
plugin_config = getattr(self, '_plugin_config', {})
if not plugin_config:
return default
# 支持嵌套键访问
keys = key.split('.')
current = plugin_config
for k in keys:
if isinstance(current, dict) and k in current:
current = current[k]
else:
return default
return current
async def get_user_id_by_person_name(self, person_name: str) -> tuple[str, str]:
"""根据用户名获取用户ID

View File

@@ -160,201 +160,7 @@ class MessageAPI:
message_type="text", content=text, platform=platform, target_id=user_id, is_group=False
)
async def send_message(self, type: str, data: str, target: Optional[str] = "", display_message: str = "") -> bool:
"""发送消息的简化方法
Args:
type: 消息类型,如"text""image"
data: 消息内容
target: 目标消息(可选)
display_message: 显示的消息内容(可选)
Returns:
bool: 是否发送成功
"""
try:
# 安全获取服务和日志前缀
services = getattr(self, "_services", {})
log_prefix = getattr(self, "log_prefix", "[MessageAPI]")
expressor: DefaultExpressor = services.get("expressor")
chat_stream: ChatStream = services.get("chat_stream")
if not expressor or not chat_stream:
logger.error(f"{log_prefix} 无法发送消息:缺少必要的内部服务")
return False
# 获取锚定消息(如果有)
observations = services.get("observations", [])
if len(observations) > 0:
chatting_observation: ChattingObservation = next(
(obs for obs in observations if isinstance(obs, ChattingObservation)), None
)
if chatting_observation:
anchor_message = chatting_observation.search_message_by_text(target)
else:
anchor_message = None
else:
anchor_message = None
# 如果没有找到锚点消息,创建一个占位符
if not anchor_message:
logger.info(f"{log_prefix} 未找到锚点消息,创建占位符")
anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream
)
else:
anchor_message.update_chat_stream(chat_stream)
response_set = [
(type, data),
]
# 调用内部方法发送消息
success = await expressor.send_response_messages(
anchor_message=anchor_message,
response_set=response_set,
display_message=display_message,
)
return success
except Exception as e:
log_prefix = getattr(self, "log_prefix", "[MessageAPI]")
logger.error(f"{log_prefix} 发送消息时出错: {e}")
traceback.print_exc()
return False
async def send_message_by_expressor(self, text: str, target: Optional[str] = None) -> bool:
"""通过expressor发送文本消息的简化方法
Args:
text: 要发送的消息文本
target: 目标消息(可选)
Returns:
bool: 是否发送成功
"""
# 安全获取服务和日志前缀
services = getattr(self, "_services", {})
log_prefix = getattr(self, "log_prefix", "[MessageAPI]")
expressor: DefaultExpressor = services.get("expressor")
chat_stream: ChatStream = services.get("chat_stream")
if not expressor or not chat_stream:
logger.error(f"{log_prefix} 无法发送消息:缺少必要的内部服务")
return False
# 构造简化的动作数据
reply_data = {"text": text, "target": target or "", "emojis": []}
# 获取锚定消息(如果有)
observations = services.get("observations", [])
# 查找 ChattingObservation 实例
chatting_observation = None
for obs in observations:
if isinstance(obs, ChattingObservation):
chatting_observation = obs
break
if not chatting_observation:
logger.warning(f"{log_prefix} 未找到 ChattingObservation 实例,创建占位符")
anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream
)
else:
anchor_message = chatting_observation.search_message_by_text(reply_data["target"])
if not anchor_message:
logger.info(f"{log_prefix} 未找到锚点消息,创建占位符")
anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream
)
else:
anchor_message.update_chat_stream(chat_stream)
# 调用内部方法发送消息
cycle_timers = getattr(self, "cycle_timers", {})
reasoning = getattr(self, "reasoning", "插件生成")
thinking_id = getattr(self, "thinking_id", "plugin_thinking")
success, _ = await expressor.deal_reply(
cycle_timers=cycle_timers,
action_data=reply_data,
anchor_message=anchor_message,
reasoning=reasoning,
thinking_id=thinking_id,
)
return success
async def send_message_by_replyer(
self, target: Optional[str] = None, extra_info_block: Optional[str] = None
) -> bool:
"""通过replyer发送消息的简化方法
Args:
target: 目标消息(可选)
extra_info_block: 额外信息块(可选)
Returns:
bool: 是否发送成功
"""
# 安全获取服务和日志前缀
services = getattr(self, "_services", {})
log_prefix = getattr(self, "log_prefix", "[MessageAPI]")
replyer: DefaultReplyer = services.get("replyer")
chat_stream: ChatStream = services.get("chat_stream")
if not replyer or not chat_stream:
logger.error(f"{log_prefix} 无法发送消息:缺少必要的内部服务")
return False
# 构造简化的动作数据
reply_data = {"target": target or "", "extra_info_block": extra_info_block}
# 获取锚定消息(如果有)
observations = services.get("observations", [])
# 查找 ChattingObservation 实例
chatting_observation = None
for obs in observations:
if isinstance(obs, ChattingObservation):
chatting_observation = obs
break
if not chatting_observation:
logger.warning(f"{log_prefix} 未找到 ChattingObservation 实例,创建占位符")
anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream
)
else:
anchor_message = chatting_observation.search_message_by_text(reply_data["target"])
if not anchor_message:
logger.info(f"{log_prefix} 未找到锚点消息,创建占位符")
anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream
)
else:
anchor_message.update_chat_stream(chat_stream)
# 调用内部方法发送消息
cycle_timers = getattr(self, "cycle_timers", {})
reasoning = getattr(self, "reasoning", "插件生成")
thinking_id = getattr(self, "thinking_id", "plugin_thinking")
success, _ = await replyer.deal_reply(
cycle_timers=cycle_timers,
action_data=reply_data,
anchor_message=anchor_message,
reasoning=reasoning,
thinking_id=thinking_id,
)
return success
def get_chat_type(self) -> str:
"""获取当前聊天类型

View File

@@ -33,7 +33,7 @@ class PluginAPI(MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI,
"""
def __init__(
self, chat_stream=None, expressor=None, replyer=None, observations=None, log_prefix: str = "[PluginAPI]"
self, chat_stream=None, expressor=None, replyer=None, observations=None, log_prefix: str = "[PluginAPI]", plugin_config: dict = None
):
"""
初始化插件API
@@ -44,6 +44,7 @@ class PluginAPI(MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI,
replyer: 回复器对象
observations: 观察列表
log_prefix: 日志前缀
plugin_config: 插件配置字典
"""
# 存储依赖对象
self._services = {
@@ -61,6 +62,9 @@ class PluginAPI(MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI,
# 调用所有父类的初始化
super().__init__()
# 存储插件配置
self._plugin_config = plugin_config or {}
logger.debug(f"{self.log_prefix} PluginAPI 初始化完成")
def set_chat_stream(self, chat_stream):
@@ -105,7 +109,7 @@ class PluginAPI(MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI,
# 便捷的工厂函数
def create_plugin_api(
chat_stream=None, expressor=None, replyer=None, observations=None, log_prefix: str = "[Plugin]"
chat_stream=None, expressor=None, replyer=None, observations=None, log_prefix: str = "[Plugin]", plugin_config: dict = None
) -> PluginAPI:
"""
创建插件API实例的便捷函数
@@ -116,12 +120,13 @@ def create_plugin_api(
replyer: 回复器对象
observations: 观察列表
log_prefix: 日志前缀
plugin_config: 插件配置字典
Returns:
PluginAPI: 配置好的插件API实例
"""
return PluginAPI(
chat_stream=chat_stream, expressor=expressor, replyer=replyer, observations=observations, log_prefix=log_prefix
chat_stream=chat_stream, expressor=expressor, replyer=replyer, observations=observations, log_prefix=log_prefix, plugin_config=plugin_config
)

View File

@@ -35,6 +35,7 @@ class BaseAction(ABC):
chat_stream=None,
log_prefix: str = "",
shutting_down: bool = False,
plugin_config: dict = None,
**kwargs,
):
"""初始化Action组件
@@ -50,6 +51,7 @@ class BaseAction(ABC):
chat_stream: 聊天流对象
log_prefix: 日志前缀
shutting_down: 是否正在关闭
plugin_config: 插件配置字典
**kwargs: 其他参数
"""
self.action_data = action_data
@@ -84,6 +86,7 @@ class BaseAction(ABC):
replyer=replyer or kwargs.get("replyer"),
observations=observations or kwargs.get("observations", []),
log_prefix=log_prefix,
plugin_config=plugin_config or kwargs.get("plugin_config"),
)
# 设置API的action上下文
@@ -118,7 +121,221 @@ class BaseAction(ABC):
Returns:
bool: 是否发送成功
"""
return await self.api.send_message("text", content)
chat_stream = self.api.get_service('chat_stream')
if not chat_stream:
logger.error(f"{self.log_prefix} 没有可用的聊天流发送回复")
return False
if chat_stream.group_info:
# 群聊
return await self.api.send_text_to_group(
text=content,
group_id=str(chat_stream.group_info.group_id),
platform=chat_stream.platform
)
else:
# 私聊
return await self.api.send_text_to_user(
text=content,
user_id=str(chat_stream.user_info.user_id),
platform=chat_stream.platform
)
async def send_command(self, command_name: str, args: dict = None, display_message: str = None) -> bool:
"""发送命令消息
使用和send_reply相同的方式通过MessageAPI发送命令
Args:
command_name: 命令名称
args: 命令参数
display_message: 显示消息
Returns:
bool: 是否发送成功
"""
try:
# 构造命令数据
command_data = {
"name": command_name,
"args": args or {}
}
# 使用send_message_to_target方法发送命令
chat_stream = self.api.get_service('chat_stream')
if not chat_stream:
logger.error(f"{self.log_prefix} 没有可用的聊天流发送命令")
return False
command_content = str(command_data)
if chat_stream.group_info:
# 群聊
success = await self.api.send_message_to_target(
message_type="command",
content=command_content,
platform=chat_stream.platform,
target_id=str(chat_stream.group_info.group_id),
is_group=True,
display_message=display_message or f"执行命令: {command_name}"
)
else:
# 私聊
success = await self.api.send_message_to_target(
message_type="command",
content=command_content,
platform=chat_stream.platform,
target_id=str(chat_stream.user_info.user_id),
is_group=False,
display_message=display_message or f"执行命令: {command_name}"
)
if success:
logger.info(f"{self.log_prefix} 成功发送命令: {command_name}")
else:
logger.error(f"{self.log_prefix} 发送命令失败: {command_name}")
return success
except Exception as e:
logger.error(f"{self.log_prefix} 发送命令时出错: {e}")
return False
async def send_message_by_expressor(self, text: str, target: str = "") -> bool:
"""通过expressor发送文本消息的Action专用方法
Args:
text: 要发送的消息文本
target: 目标消息(可选)
Returns:
bool: 是否发送成功
"""
try:
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.message_receive.message import create_empty_anchor_message
# 获取服务
expressor = self.api.get_service("expressor")
chat_stream = self.api.get_service("chat_stream")
observations = self.api.get_service("observations") or []
if not expressor or not chat_stream:
logger.error(f"{self.log_prefix} 无法通过expressor发送消息缺少必要的服务")
return False
# 构造动作数据
reply_data = {"text": text, "target": target, "emojis": []}
# 查找 ChattingObservation 实例
chatting_observation = None
for obs in observations:
if isinstance(obs, ChattingObservation):
chatting_observation = obs
break
if not chatting_observation:
logger.warning(f"{self.log_prefix} 未找到 ChattingObservation 实例,创建占位符")
anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream
)
else:
anchor_message = chatting_observation.search_message_by_text(target)
if not anchor_message:
logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符")
anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream
)
else:
anchor_message.update_chat_stream(chat_stream)
# 使用Action上下文信息发送消息
success, _ = await expressor.deal_reply(
cycle_timers=self.cycle_timers,
action_data=reply_data,
anchor_message=anchor_message,
reasoning=self.reasoning,
thinking_id=self.thinking_id,
)
if success:
logger.info(f"{self.log_prefix} 成功通过expressor发送消息")
else:
logger.error(f"{self.log_prefix} 通过expressor发送消息失败")
return success
except Exception as e:
logger.error(f"{self.log_prefix} 通过expressor发送消息时出错: {e}")
return False
async def send_message_by_replyer(self, target: str = "", extra_info_block: str = None) -> bool:
"""通过replyer发送消息的Action专用方法
Args:
target: 目标消息(可选)
extra_info_block: 额外信息块(可选)
Returns:
bool: 是否发送成功
"""
try:
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.message_receive.message import create_empty_anchor_message
# 获取服务
replyer = self.api.get_service("replyer")
chat_stream = self.api.get_service("chat_stream")
observations = self.api.get_service("observations") or []
if not replyer or not chat_stream:
logger.error(f"{self.log_prefix} 无法通过replyer发送消息缺少必要的服务")
return False
# 构造动作数据
reply_data = {"target": target, "extra_info_block": extra_info_block}
# 查找 ChattingObservation 实例
chatting_observation = None
for obs in observations:
if isinstance(obs, ChattingObservation):
chatting_observation = obs
break
if not chatting_observation:
logger.warning(f"{self.log_prefix} 未找到 ChattingObservation 实例,创建占位符")
anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream
)
else:
anchor_message = chatting_observation.search_message_by_text(target)
if not anchor_message:
logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符")
anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream
)
else:
anchor_message.update_chat_stream(chat_stream)
# 使用Action上下文信息发送消息
success, _ = await replyer.deal_reply(
cycle_timers=self.cycle_timers,
action_data=reply_data,
anchor_message=anchor_message,
reasoning=self.reasoning,
thinking_id=self.thinking_id,
)
if success:
logger.info(f"{self.log_prefix} 成功通过replyer发送消息")
else:
logger.error(f"{self.log_prefix} 通过replyer发送消息失败")
return success
except Exception as e:
logger.error(f"{self.log_prefix} 通过replyer发送消息时出错: {e}")
return False
@classmethod
def get_action_info(cls, name: str = None, description: str = None) -> "ActionInfo":
@@ -132,12 +349,14 @@ class BaseAction(ABC):
ActionInfo: 生成的Action信息对象
"""
# 自动生成名称和描述
# 优先使用类属性,然后自动生成
if name is None:
name = cls.__name__.lower().replace("action", "")
name = getattr(cls, "action_name", cls.__name__.lower().replace("action", ""))
if description is None:
description = cls.__doc__ or f"{cls.__name__} Action组件"
description = description.strip().split("\n")[0] # 取第一行作为描述
description = getattr(cls, "action_description", None)
if description is None:
description = cls.__doc__ or f"{cls.__name__} Action组件"
description = description.strip().split("\n")[0] # 取第一行作为描述
# 安全获取激活类型值
def get_enum_value(attr_name, default):

View File

@@ -17,24 +17,27 @@ class BaseCommand(ABC):
- command_pattern: 命令匹配的正则表达式
- command_help: 命令帮助信息
- command_examples: 命令使用示例列表
- intercept_message: 是否拦截消息处理默认True拦截False继续传递
"""
# 默认命令设置(子类可以覆盖)
command_pattern: str = ""
command_help: str = ""
command_examples: List[str] = []
intercept_message: bool = True # 默认拦截消息,不继续处理
def __init__(self, message: MessageRecv):
def __init__(self, message: MessageRecv, plugin_config: dict = None):
"""初始化Command组件
Args:
message: 接收到的消息对象
plugin_config: 插件配置字典
"""
self.message = message
self.matched_groups: Dict[str, str] = {} # 存储正则表达式匹配的命名组
# 创建API实例
self.api = PluginAPI(chat_stream=message.chat_stream, log_prefix="[Command]")
self.api = PluginAPI(chat_stream=message.chat_stream, log_prefix="[Command]", plugin_config=plugin_config)
self.log_prefix = "[Command]"
@@ -77,6 +80,62 @@ class BaseCommand(ABC):
text=content, user_id=str(chat_stream.user_info.user_id), platform=chat_stream.platform
)
async def send_command(self, command_name: str, args: dict = None, display_message: str = None) -> bool:
"""发送命令消息
使用和send_reply相同的方式通过MessageAPI发送命令
Args:
command_name: 命令名称
args: 命令参数
display_message: 显示消息
Returns:
bool: 是否发送成功
"""
try:
# 构造命令数据
command_data = {
"name": command_name,
"args": args or {}
}
# 使用send_message_to_target方法发送命令
chat_stream = self.message.chat_stream
command_content = str(command_data)
if chat_stream.group_info:
# 群聊
success = await self.api.send_message_to_target(
message_type="command",
content=command_content,
platform=chat_stream.platform,
target_id=str(chat_stream.group_info.group_id),
is_group=True,
display_message=display_message or f"执行命令: {command_name}"
)
else:
# 私聊
success = await self.api.send_message_to_target(
message_type="command",
content=command_content,
platform=chat_stream.platform,
target_id=str(chat_stream.user_info.user_id),
is_group=False,
display_message=display_message or f"执行命令: {command_name}"
)
if success:
logger.info(f"{self.log_prefix} 成功发送命令: {command_name}")
else:
logger.error(f"{self.log_prefix} 发送命令失败: {command_name}")
return success
except Exception as e:
logger.error(f"{self.log_prefix} 发送命令时出错: {e}")
return False
@classmethod
def get_command_info(cls, name: str = None, description: str = None) -> "CommandInfo":
"""从类属性生成CommandInfo
@@ -89,12 +148,14 @@ class BaseCommand(ABC):
CommandInfo: 生成的Command信息对象
"""
# 自动生成名称和描述
# 优先使用类属性,然后自动生成
if name is None:
name = cls.__name__.lower().replace("command", "")
name = getattr(cls, "command_name", cls.__name__.lower().replace("command", ""))
if description is None:
description = cls.__doc__ or f"{cls.__name__} Command组件"
description = description.strip().split("\n")[0] # 取第一行作为描述
description = getattr(cls, "command_description", None)
if description is None:
description = cls.__doc__ or f"{cls.__name__} Command组件"
description = description.strip().split("\n")[0] # 取第一行作为描述
return CommandInfo(
name=name,
@@ -103,4 +164,5 @@ class BaseCommand(ABC):
command_pattern=cls.command_pattern,
command_help=cls.command_help,
command_examples=cls.command_examples.copy() if cls.command_examples else [],
intercept_message=cls.intercept_message,
)

View File

@@ -86,6 +86,7 @@ class CommandInfo(ComponentInfo):
command_pattern: str = "" # 命令匹配模式(正则表达式)
command_help: str = "" # 命令帮助信息
command_examples: List[str] = None # 命令使用示例
intercept_message: bool = True # 是否拦截消息处理(默认拦截)
def __post_init__(self):
super().__post_init__()

View File

@@ -141,14 +141,14 @@ class ComponentRegistry:
info = self.get_component_info(command_name)
return info if isinstance(info, CommandInfo) else None
def find_command_by_text(self, text: str) -> Optional[tuple[Type, dict]]:
def find_command_by_text(self, text: str) -> Optional[tuple[Type, dict, bool, str]]:
"""根据文本查找匹配的命令
Args:
text: 输入文本
Returns:
Optional[tuple[Type, dict]]: (命令类, 匹配的命名组) 或 None
Optional[tuple[Type, dict, bool, str]]: (命令类, 匹配的命名组, 是否拦截消息, 插件名) 或 None
"""
for pattern, command_class in self._command_patterns.items():
match = pattern.match(text)
@@ -164,7 +164,7 @@ class ComponentRegistry:
if command_name:
command_info = self.get_command_info(command_name)
if command_info and command_info.enabled:
return command_class, match.groupdict()
return command_class, match.groupdict(), command_info.intercept_message, command_info.plugin_name
return None
# === 插件管理方法 ===
@@ -205,6 +205,20 @@ class ComponentRegistry:
plugin_info = self.get_plugin_info(plugin_name)
return plugin_info.components if plugin_info else []
def get_plugin_config(self, plugin_name: str) -> Optional[dict]:
"""获取插件配置
Args:
plugin_name: 插件名称
Returns:
Optional[dict]: 插件配置字典或None
"""
# 从插件管理器获取插件实例的配置
from src.plugin_system.core.plugin_manager import plugin_manager
plugin_instance = plugin_manager.get_plugin_instance(plugin_name)
return plugin_instance.config if plugin_instance else None
# === 状态管理方法 ===
def enable_component(self, component_name: str) -> bool:

View File

@@ -2,6 +2,7 @@ from typing import Dict, List, Optional, Any, TYPE_CHECKING
import os
import importlib
import importlib.util
import inspect
from pathlib import Path
if TYPE_CHECKING:
@@ -72,9 +73,10 @@ class PluginManager:
if plugin_dir:
self.plugin_paths[plugin_name] = plugin_dir
if instantiate_and_register_plugin(plugin_class, plugin_dir):
plugin_instance = plugin_class(plugin_dir=plugin_dir)
if plugin_instance.register_plugin():
total_registered += 1
self.loaded_plugins[plugin_name] = plugin_class
self.loaded_plugins[plugin_name] = plugin_instance
# 📊 显示插件详细信息
plugin_info = component_registry.get_plugin_info(plugin_name)
@@ -288,6 +290,17 @@ class PluginManager:
return True
return False
def get_plugin_instance(self, plugin_name: str) -> Optional["BasePlugin"]:
"""获取插件实例
Args:
plugin_name: 插件名称
Returns:
Optional[BasePlugin]: 插件实例或None
"""
return self.loaded_plugins.get(plugin_name)
def get_plugin_stats(self) -> Dict[str, Any]:
"""获取插件统计信息"""
all_plugins = component_registry.get_all_plugins()