Merge branch 'dev' of https://github.com/MaiM-with-u/MaiBot into dev
This commit is contained in:
31
.github/workflows/docker-image.yml
vendored
31
.github/workflows/docker-image.yml
vendored
@@ -1,27 +1,23 @@
|
||||
name: Docker Build and Push
|
||||
|
||||
on:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# - classical
|
||||
# - dev
|
||||
# tags:
|
||||
# - "v*.*.*"
|
||||
# - "v*"
|
||||
# - "*.*.*"
|
||||
# - "*.*.*-*"
|
||||
workflow_dispatch: # 允许手动触发工作流
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- classical
|
||||
- dev
|
||||
- dev-refactor
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
- "v*"
|
||||
- "*.*.*"
|
||||
- "*.*.*-*"
|
||||
workflow_dispatch: # 允许手动触发工作流
|
||||
|
||||
# Workflow's jobs
|
||||
jobs:
|
||||
build-amd64:
|
||||
name: Build AMD64 Image
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
@@ -74,7 +70,7 @@ jobs:
|
||||
|
||||
build-arm64:
|
||||
name: Build ARM64 Image
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04-arm
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
@@ -90,11 +86,6 @@ jobs:
|
||||
- name: Clone lpmm
|
||||
run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
@@ -132,7 +123,7 @@ jobs:
|
||||
|
||||
create-manifest:
|
||||
name: Create Multi-Arch Manifest
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- build-amd64
|
||||
- build-arm64
|
||||
|
||||
@@ -22,7 +22,7 @@ class ExampleAction(BaseAction):
|
||||
action_name = "example_action" # 动作的唯一标识符
|
||||
action_description = "这是一个示例动作" # 动作描述
|
||||
activation_type = ActionActivationType.ALWAYS # 这里以 ALWAYS 为例
|
||||
mode_enable = ChatMode.ALL # 这里以 ALL 为例
|
||||
mode_enable = ChatMode.ALL # 一般取ALL,表示在所有聊天模式下都可用
|
||||
associated_types = ["text", "emoji", ...] # 关联类型
|
||||
parallel_action = False # 是否允许与其他Action并行执行
|
||||
action_parameters = {"param1": "参数1的说明", "param2": "参数2的说明", ...}
|
||||
@@ -60,7 +60,7 @@ class ExampleAction(BaseAction):
|
||||
**请知悉,对于不同的处理器,其支持的消息类型可能会有所不同。在开发时请注意。**
|
||||
|
||||
#### action_parameters: 该Action的参数说明。
|
||||
这是一个字典,键为参数名,值为参数说明。这个字段可以帮助LLM理解如何使用这个Action,并由LLM返回对应的参数,最后传递到 Action 的 action_data 属性中。其格式与你定义的格式完全相同 **(除非LLM哈气了,返回了错误的内容)**。
|
||||
这是一个字典,键为参数名,值为参数说明。这个字段可以帮助LLM理解如何使用这个Action,并由LLM返回对应的参数,最后传递到 Action 的 **`action_data`** 属性中。其格式与你定义的格式完全相同 **(除非LLM哈气了,返回了错误的内容)**。
|
||||
|
||||
---
|
||||
|
||||
@@ -180,6 +180,8 @@ class GreetingAction(BaseAction):
|
||||
return True, "发送了问候"
|
||||
```
|
||||
|
||||
一个完整的使用`ActionActivationType.KEYWORD`的例子请参考`plugins/hello_world_plugin`中的`ByeAction`。
|
||||
|
||||
#### 第二层:使用决策
|
||||
|
||||
**在Action被激活后,使用条件决定麦麦什么时候会"选择"使用这个Action**。
|
||||
|
||||
@@ -6,34 +6,6 @@
|
||||
>
|
||||
> 系统会根据你在代码中定义的 `config_schema` 自动生成配置文件。手动创建配置文件会破坏自动化流程,导致配置不一致、缺失注释和文档等问题。
|
||||
|
||||
## 📖 目录
|
||||
|
||||
1. [配置架构变更说明](#配置架构变更说明)
|
||||
2. [配置版本管理](#配置版本管理)
|
||||
3. [配置定义:Schema驱动的配置系统](#配置定义schema驱动的配置系统)
|
||||
4. [配置访问:在Action和Command中使用配置](#配置访问在action和command中使用配置)
|
||||
5. [完整示例:从定义到使用](#完整示例从定义到使用)
|
||||
6. [最佳实践与注意事项](#最佳实践与注意事项)
|
||||
|
||||
---
|
||||
|
||||
## 配置架构变更说明
|
||||
|
||||
- **`_manifest.json`** - 负责插件的**元数据信息**(静态)
|
||||
- 插件名称、版本、描述
|
||||
- 作者信息、许可证
|
||||
- 仓库链接、关键词、分类
|
||||
- 组件列表、兼容性信息
|
||||
|
||||
- **`config.toml`** - 负责插件的**运行时配置**(动态)
|
||||
- `enabled` - 是否启用插件
|
||||
- 功能参数配置
|
||||
- 组件启用开关
|
||||
- 用户可调整的行为参数
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 配置版本管理
|
||||
|
||||
### 🎯 版本管理概述
|
||||
@@ -103,7 +75,7 @@ config_schema = {
|
||||
2. **迁移配置值** - 将旧配置文件中的值迁移到新结构中
|
||||
3. **处理新增字段** - 新增的配置项使用默认值
|
||||
4. **更新版本号** - `config_version` 字段自动更新为最新版本
|
||||
5. **保存配置文件** - 迁移后的配置直接覆盖原文件(不保留备份)
|
||||
5. **保存配置文件** - 迁移后的配置直接覆盖原文件**(不保留备份)**
|
||||
|
||||
### 🔧 实际使用示例
|
||||
|
||||
@@ -174,28 +146,13 @@ min_duration = 120
|
||||
- 跳过版本检查和迁移
|
||||
- 直接加载现有配置
|
||||
- 新增的配置项在代码中使用默认值访问
|
||||
|
||||
### 📝 配置迁移日志
|
||||
|
||||
系统会详细记录配置迁移过程:
|
||||
|
||||
```log
|
||||
[MutePlugin] 检测到配置版本需要更新: 当前=v1.0.0, 期望=v1.1.0
|
||||
[MutePlugin] 生成新配置结构...
|
||||
[MutePlugin] 迁移配置值: plugin.enabled = true
|
||||
[MutePlugin] 更新配置版本: plugin.config_version = 1.1.0 (旧值: 1.0.0)
|
||||
[MutePlugin] 迁移配置值: mute.min_duration = 120
|
||||
[MutePlugin] 迁移配置值: mute.max_duration = 3600
|
||||
[MutePlugin] 新增节: permissions
|
||||
[MutePlugin] 配置文件已从 v1.0.0 更新到 v1.1.0
|
||||
```
|
||||
- 系统会详细记录配置迁移过程。
|
||||
|
||||
### ⚠️ 重要注意事项
|
||||
|
||||
#### 1. 版本号管理
|
||||
- 当你修改 `config_schema` 时,**必须同步更新** `config_version`
|
||||
- 建议使用语义化版本号 (例如:`1.0.0`, `1.1.0`, `2.0.0`)
|
||||
- 配置结构的重大变更应该增加主版本号
|
||||
- 请使用语义化版本号 (例如:`1.0.0`, `1.1.0`, `2.0.0`)
|
||||
|
||||
#### 2. 迁移策略
|
||||
- **保留原值优先**: 迁移时优先保留用户的原有配置值
|
||||
@@ -207,45 +164,7 @@ min_duration = 120
|
||||
- **不保留备份**: 迁移后直接覆盖原配置文件,不保留备份
|
||||
- **失败安全**: 如果迁移过程中出现错误,会回退到原配置
|
||||
|
||||
---
|
||||
|
||||
## 配置定义:Schema驱动的配置系统
|
||||
|
||||
### 核心理念:Schema驱动的配置
|
||||
|
||||
在新版插件系统中,我们引入了一套 **配置Schema(模式)驱动** 的机制。**你不需要也不应该手动创建和维护 `config.toml` 文件**,而是通过在插件代码中 **声明配置的结构**,系统将为你完成剩下的工作。
|
||||
|
||||
> **⚠️ 绝对不要手动创建 config.toml 文件!**
|
||||
>
|
||||
> - ❌ **错误做法**:手动在插件目录下创建 `config.toml` 文件
|
||||
> - ✅ **正确做法**:在插件代码中定义 `config_schema`,让系统自动生成配置文件
|
||||
|
||||
**核心优势:**
|
||||
|
||||
- **自动化 (Automation)**: 如果配置文件不存在,系统会根据你的声明 **自动生成** 一份包含默认值和详细注释的 `config.toml` 文件。
|
||||
- **规范化 (Standardization)**: 所有插件的配置都遵循统一的结构,提升了可维护性。
|
||||
- **自带文档 (Self-documenting)**: 配置文件中的每一项都包含详细的注释、类型说明、可选值和示例,极大地降低了用户的使用门槛。
|
||||
- **健壮性 (Robustness)**: 在代码中直接定义配置的类型和默认值,减少了因配置错误导致的运行时问题。
|
||||
- **易于管理 (Easy Management)**: 生成的配置文件可以方便地加入 `.gitignore`,避免将个人配置(如API Key)提交到版本库。
|
||||
|
||||
### 配置生成工作流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[编写插件代码] --> B[定义 config_schema]
|
||||
B --> C[首次加载插件]
|
||||
C --> D{config.toml 是否存在?}
|
||||
D -->|不存在| E[系统自动生成 config.toml]
|
||||
D -->|存在| F[加载现有配置文件]
|
||||
E --> G[配置完成,插件可用]
|
||||
F --> G
|
||||
|
||||
style E fill:#90EE90
|
||||
style B fill:#87CEEB
|
||||
style G fill:#DDA0DD
|
||||
```
|
||||
|
||||
### 如何定义配置
|
||||
## 配置定义
|
||||
|
||||
配置的定义在你的插件主类(继承自 `BasePlugin`)中完成,主要通过两个类属性:
|
||||
|
||||
@@ -257,6 +176,7 @@ graph TD
|
||||
每个配置项都通过一个 `ConfigField` 对象来定义。
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from src.plugin_system.base.config_types import ConfigField
|
||||
|
||||
@dataclass
|
||||
@@ -270,28 +190,21 @@ class ConfigField:
|
||||
choices: Optional[List[Any]] = None # 可选值列表 (可选)
|
||||
```
|
||||
|
||||
### 配置定义示例
|
||||
### 配置示例
|
||||
|
||||
让我们以一个功能丰富的 `MutePlugin` 为例,看看如何定义它的配置。
|
||||
|
||||
```python
|
||||
# src/plugins/built_in/mute_plugin/plugin.py
|
||||
|
||||
from src.plugin_system import BasePlugin, register_plugin
|
||||
from src.plugin_system.base.config_types import ConfigField
|
||||
from src.plugin_system import BasePlugin, register_plugin, ConfigField
|
||||
from typing import List, Tuple, Type
|
||||
|
||||
@register_plugin
|
||||
class MutePlugin(BasePlugin):
|
||||
"""禁言插件"""
|
||||
|
||||
# 插件基本信息
|
||||
plugin_name = "mute_plugin"
|
||||
plugin_description = "群聊禁言管理插件,提供智能禁言功能"
|
||||
plugin_version = "2.0.0"
|
||||
plugin_author = "MaiBot开发团队"
|
||||
enable_plugin = True
|
||||
config_file_name = "config.toml"
|
||||
# 这里是插件基本信息,略去
|
||||
|
||||
# 步骤1: 定义配置节的描述
|
||||
config_section_descriptions = {
|
||||
@@ -339,22 +252,9 @@ class MutePlugin(BasePlugin):
|
||||
}
|
||||
}
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
# 在这里可以通过 self.get_config() 来获取配置值
|
||||
enable_smart_mute = self.get_config("components.enable_smart_mute", True)
|
||||
enable_mute_command = self.get_config("components.enable_mute_command", False)
|
||||
|
||||
components = []
|
||||
if enable_smart_mute:
|
||||
components.append((SmartMuteAction.get_action_info(), SmartMuteAction))
|
||||
if enable_mute_command:
|
||||
components.append((MuteCommand.get_command_info(), MuteCommand))
|
||||
|
||||
return components
|
||||
# 这里是插件方法,略去
|
||||
```
|
||||
|
||||
### 自动生成的配置文件
|
||||
|
||||
当 `mute_plugin` 首次加载且其目录中不存在 `config.toml` 时,系统会自动创建以下文件:
|
||||
|
||||
```toml
|
||||
@@ -413,317 +313,24 @@ prefix = "[MutePlugin]"
|
||||
|
||||
---
|
||||
|
||||
## 配置访问:在Action和Command中使用配置
|
||||
## 配置访问
|
||||
|
||||
### 问题描述
|
||||
如果你想要在你的组件中访问配置,可以通过组件内置的 `get_config()` 方法访问配置。
|
||||
|
||||
在插件开发中,你可能遇到这样的问题:
|
||||
- 想要在Action或Command中访问插件配置
|
||||
|
||||
### ✅ 解决方案
|
||||
|
||||
**直接使用 `self.get_config()` 方法!**
|
||||
|
||||
系统已经自动为你处理了配置传递,你只需要通过组件内置的 `get_config` 方法访问配置即可。
|
||||
|
||||
### 📖 快速示例
|
||||
|
||||
#### 在Action中访问配置
|
||||
其参数为一个命名空间化的字符串。以上面的 `MutePlugin` 为例,你可以这样访问配置:
|
||||
|
||||
```python
|
||||
from src.plugin_system import BaseAction
|
||||
|
||||
class MyAction(BaseAction):
|
||||
async def execute(self):
|
||||
# 方法1: 获取配置值(带默认值)
|
||||
api_key = self.get_config("api.key", "default_key")
|
||||
timeout = self.get_config("api.timeout", 30)
|
||||
|
||||
# 方法2: 支持嵌套键访问
|
||||
log_level = self.get_config("advanced.logging.level", "INFO")
|
||||
|
||||
# 方法3: 直接访问顶层配置
|
||||
enable_feature = self.get_config("features.enable_smart", False)
|
||||
|
||||
# 使用配置值
|
||||
if enable_feature:
|
||||
await self.send_text(f"API密钥: {api_key}")
|
||||
|
||||
return True, "配置访问成功"
|
||||
enable_smart_mute = self.get_config("components.enable_smart_mute", True)
|
||||
```
|
||||
|
||||
#### 在Command中访问配置
|
||||
|
||||
```python
|
||||
from src.plugin_system import BaseCommand
|
||||
|
||||
class MyCommand(BaseCommand):
|
||||
async def execute(self):
|
||||
# 使用方式与Action完全相同
|
||||
welcome_msg = self.get_config("messages.welcome", "欢迎!")
|
||||
max_results = self.get_config("search.max_results", 10)
|
||||
|
||||
# 根据配置执行不同逻辑
|
||||
if self.get_config("features.debug_mode", False):
|
||||
await self.send_text(f"调试模式已启用,最大结果数: {max_results}")
|
||||
|
||||
await self.send_text(welcome_msg)
|
||||
return True, "命令执行完成"
|
||||
```
|
||||
|
||||
### 🔧 API方法详解
|
||||
|
||||
#### 1. `get_config(key, default=None)`
|
||||
|
||||
获取配置值,支持嵌套键访问:
|
||||
|
||||
```python
|
||||
# 简单键
|
||||
value = self.get_config("timeout", 30)
|
||||
|
||||
# 嵌套键(用点号分隔)
|
||||
value = self.get_config("database.connection.host", "localhost")
|
||||
value = self.get_config("features.ai.model", "gpt-3.5-turbo")
|
||||
```
|
||||
|
||||
#### 2. 类型安全的配置访问
|
||||
|
||||
```python
|
||||
# 确保正确的类型
|
||||
max_retries = self.get_config("api.max_retries", 3)
|
||||
if not isinstance(max_retries, int):
|
||||
max_retries = 3 # 使用安全的默认值
|
||||
|
||||
# 布尔值配置
|
||||
debug_mode = self.get_config("features.debug_mode", False)
|
||||
if debug_mode:
|
||||
# 调试功能逻辑
|
||||
pass
|
||||
```
|
||||
|
||||
#### 3. 配置驱动的组件行为
|
||||
|
||||
```python
|
||||
class ConfigDrivenAction(BaseAction):
|
||||
async def execute(self):
|
||||
# 根据配置决定激活行为
|
||||
activation_config = {
|
||||
"use_keywords": self.get_config("activation.use_keywords", True),
|
||||
"use_llm": self.get_config("activation.use_llm", False),
|
||||
"keywords": self.get_config("activation.keywords", []),
|
||||
}
|
||||
|
||||
# 根据配置调整功能
|
||||
features = {
|
||||
"enable_emoji": self.get_config("features.enable_emoji", True),
|
||||
"enable_llm_reply": self.get_config("features.enable_llm_reply", False),
|
||||
"max_length": self.get_config("output.max_length", 200),
|
||||
}
|
||||
|
||||
# 使用配置执行逻辑
|
||||
if features["enable_llm_reply"]:
|
||||
# 使用LLM生成回复
|
||||
pass
|
||||
else:
|
||||
# 使用模板回复
|
||||
pass
|
||||
|
||||
return True, "配置驱动执行完成"
|
||||
```
|
||||
|
||||
### 🔄 配置传递机制
|
||||
|
||||
系统自动处理配置传递,无需手动操作:
|
||||
|
||||
1. **插件初始化** → `BasePlugin`加载`config.toml`到`self.config`
|
||||
2. **组件注册** → 系统记录插件配置
|
||||
3. **组件实例化** → 自动传递`plugin_config`参数给Action/Command
|
||||
4. **配置访问** → 组件通过`self.get_config()`直接访问配置
|
||||
|
||||
---
|
||||
|
||||
## 完整示例:从定义到使用
|
||||
|
||||
### 插件定义
|
||||
|
||||
```python
|
||||
from src.plugin_system.base.config_types import ConfigField
|
||||
|
||||
@register_plugin
|
||||
class GreetingPlugin(BasePlugin):
|
||||
"""问候插件完整示例"""
|
||||
|
||||
plugin_name = "greeting_plugin"
|
||||
plugin_description = "智能问候插件,展示配置定义和访问的完整流程"
|
||||
plugin_version = "1.0.0"
|
||||
config_file_name = "config.toml"
|
||||
|
||||
# 配置节描述
|
||||
config_section_descriptions = {
|
||||
"plugin": "插件启用配置",
|
||||
"greeting": "问候功能配置",
|
||||
"features": "功能开关配置",
|
||||
"messages": "消息模板配置"
|
||||
}
|
||||
|
||||
# 配置Schema定义
|
||||
config_schema = {
|
||||
"plugin": {
|
||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件")
|
||||
},
|
||||
"greeting": {
|
||||
"template": ConfigField(
|
||||
type=str,
|
||||
default="你好,{username}!欢迎使用问候插件!",
|
||||
description="问候消息模板"
|
||||
),
|
||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号"),
|
||||
"enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成个性化问候")
|
||||
},
|
||||
"features": {
|
||||
"smart_detection": ConfigField(type=bool, default=True, description="是否启用智能检测"),
|
||||
"random_greeting": ConfigField(type=bool, default=False, description="是否使用随机问候语"),
|
||||
"max_greetings_per_hour": ConfigField(type=int, default=5, description="每小时最大问候次数")
|
||||
},
|
||||
"messages": {
|
||||
"custom_greetings": ConfigField(
|
||||
type=list,
|
||||
default=["你好!", "嗨!", "欢迎!"],
|
||||
description="自定义问候语列表"
|
||||
),
|
||||
"error_message": ConfigField(
|
||||
type=str,
|
||||
default="问候功能暂时不可用",
|
||||
description="错误时显示的消息"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
"""根据配置动态注册组件"""
|
||||
components = []
|
||||
|
||||
# 根据配置决定是否注册组件
|
||||
if self.get_config("plugin.enabled", True):
|
||||
components.append((SmartGreetingAction.get_action_info(), SmartGreetingAction))
|
||||
components.append((GreetingCommand.get_command_info(), GreetingCommand))
|
||||
|
||||
return components
|
||||
```
|
||||
|
||||
### Action组件使用配置
|
||||
|
||||
```python
|
||||
class SmartGreetingAction(BaseAction):
|
||||
"""智能问候Action - 展示配置访问"""
|
||||
|
||||
focus_activation_type = ActionActivationType.KEYWORD
|
||||
normal_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["你好", "hello", "hi"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
"""执行智能问候,大量使用配置"""
|
||||
try:
|
||||
# 检查插件是否启用
|
||||
if not self.get_config("plugin.enabled", True):
|
||||
return False, "插件已禁用"
|
||||
|
||||
# 获取问候配置
|
||||
template = self.get_config("greeting.template", "你好,{username}!")
|
||||
enable_emoji = self.get_config("greeting.enable_emoji", True)
|
||||
enable_llm = self.get_config("greeting.enable_llm", False)
|
||||
|
||||
# 获取功能配置
|
||||
smart_detection = self.get_config("features.smart_detection", True)
|
||||
random_greeting = self.get_config("features.random_greeting", False)
|
||||
max_per_hour = self.get_config("features.max_greetings_per_hour", 5)
|
||||
|
||||
# 获取消息配置
|
||||
custom_greetings = self.get_config("messages.custom_greetings", [])
|
||||
error_message = self.get_config("messages.error_message", "问候功能不可用")
|
||||
|
||||
# 根据配置执行不同逻辑
|
||||
username = self.action_data.get("username", "用户")
|
||||
|
||||
if random_greeting and custom_greetings:
|
||||
# 使用随机自定义问候语
|
||||
import random
|
||||
greeting_msg = random.choice(custom_greetings)
|
||||
elif enable_llm:
|
||||
# 使用LLM生成个性化问候
|
||||
greeting_msg = await self._generate_llm_greeting(username)
|
||||
else:
|
||||
# 使用模板问候
|
||||
greeting_msg = template.format(username=username)
|
||||
|
||||
# 发送问候消息
|
||||
await self.send_text(greeting_msg)
|
||||
|
||||
# 根据配置发送表情
|
||||
if enable_emoji:
|
||||
await self.send_emoji("😊")
|
||||
|
||||
return True, f"向{username}发送了问候"
|
||||
|
||||
except Exception as e:
|
||||
# 使用配置的错误消息
|
||||
await self.send_text(self.get_config("messages.error_message", "出错了"))
|
||||
return False, f"问候失败: {str(e)}"
|
||||
|
||||
async def _generate_llm_greeting(self, username: str) -> str:
|
||||
"""根据配置使用LLM生成问候语"""
|
||||
# 这里可以进一步使用配置来定制LLM行为
|
||||
llm_style = self.get_config("greeting.llm_style", "friendly")
|
||||
# ... LLM调用逻辑
|
||||
return f"你好 {username}!很高兴见到你!"
|
||||
```
|
||||
|
||||
### Command组件使用配置
|
||||
|
||||
```python
|
||||
class GreetingCommand(BaseCommand):
|
||||
"""问候命令 - 展示配置访问"""
|
||||
|
||||
command_pattern = r"^/greet(?:\s+(?P<username>\w+))?$"
|
||||
command_help = "发送问候消息"
|
||||
command_examples = ["/greet", "/greet Alice"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
||||
"""执行问候命令"""
|
||||
# 检查功能是否启用
|
||||
if not self.get_config("plugin.enabled", True):
|
||||
await self.send_text("问候功能已禁用")
|
||||
return False, "功能禁用"
|
||||
|
||||
# 获取用户名
|
||||
username = self.matched_groups.get("username", "用户")
|
||||
|
||||
# 根据配置选择问候方式
|
||||
if self.get_config("features.random_greeting", False):
|
||||
custom_greetings = self.get_config("messages.custom_greetings", ["你好!"])
|
||||
import random
|
||||
greeting = random.choice(custom_greetings)
|
||||
else:
|
||||
template = self.get_config("greeting.template", "你好,{username}!")
|
||||
greeting = template.format(username=username)
|
||||
|
||||
# 发送问候
|
||||
await self.send_text(greeting)
|
||||
|
||||
# 根据配置发送表情
|
||||
if self.get_config("greeting.enable_emoji", True):
|
||||
await self.send_text("😊")
|
||||
|
||||
return True, "问候发送成功"
|
||||
```
|
||||
如果尝试访问了一个不存在的配置项,系统会自动返回默认值(你传递的)或者 `None`。
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践与注意事项
|
||||
|
||||
### 配置定义最佳实践
|
||||
|
||||
> **🚨 核心原则:永远不要手动创建 config.toml 文件!**
|
||||
**🚨 核心原则:永远不要手动创建 config.toml 文件!**
|
||||
|
||||
1. **🔥 绝不手动创建配置文件**: **任何时候都不要手动创建 `config.toml` 文件**!必须通过在 `plugin.py` 中定义 `config_schema` 让系统自动生成。
|
||||
- ❌ **禁止**:`touch config.toml`、手动编写配置文件
|
||||
@@ -738,75 +345,3 @@ class GreetingCommand(BaseCommand):
|
||||
5. **gitignore**: 将 `plugins/*/config.toml` 或 `src/plugins/built_in/*/config.toml` 加入 `.gitignore`,以避免提交个人敏感信息。
|
||||
|
||||
6. **配置文件只供修改**: 自动生成的 `config.toml` 文件只应该被用户**修改**,而不是从零创建。
|
||||
|
||||
### 配置访问最佳实践
|
||||
|
||||
#### 1. 总是提供默认值
|
||||
|
||||
```python
|
||||
# ✅ 好的做法
|
||||
timeout = self.get_config("api.timeout", 30)
|
||||
|
||||
# ❌ 避免这样做
|
||||
timeout = self.get_config("api.timeout") # 可能返回None
|
||||
```
|
||||
|
||||
#### 2. 验证配置类型
|
||||
|
||||
```python
|
||||
# 获取配置后验证类型
|
||||
max_items = self.get_config("list.max_items", 10)
|
||||
if not isinstance(max_items, int) or max_items <= 0:
|
||||
max_items = 10 # 使用安全的默认值
|
||||
```
|
||||
|
||||
#### 3. 缓存复杂配置解析
|
||||
|
||||
```python
|
||||
class MyAction(BaseAction):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 在初始化时解析复杂配置,避免重复解析
|
||||
self._api_config = self._parse_api_config()
|
||||
|
||||
def _parse_api_config(self):
|
||||
return {
|
||||
'key': self.get_config("api.key", ""),
|
||||
'timeout': self.get_config("api.timeout", 30),
|
||||
'retries': self.get_config("api.max_retries", 3)
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 配置驱动的组件注册
|
||||
|
||||
```python
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
"""根据配置动态注册组件"""
|
||||
components = []
|
||||
|
||||
# 从配置获取组件启用状态
|
||||
enable_action = self.get_config("components.enable_action", True)
|
||||
enable_command = self.get_config("components.enable_command", True)
|
||||
|
||||
if enable_action:
|
||||
components.append((MyAction.get_action_info(), MyAction))
|
||||
if enable_command:
|
||||
components.append((MyCommand.get_command_info(), MyCommand))
|
||||
|
||||
return components
|
||||
```
|
||||
|
||||
### 🎉 总结
|
||||
|
||||
现在你掌握了插件配置的完整流程:
|
||||
|
||||
1. **定义配置**: 在插件中使用 `config_schema` 定义配置结构
|
||||
2. **访问配置**: 在组件中使用 `self.get_config("key", default_value)` 访问配置
|
||||
3. **自动生成**: 系统自动生成带注释的配置文件
|
||||
4. **动态行为**: 根据配置动态调整插件行为
|
||||
|
||||
> **🚨 最后强调:任何时候都不要手动创建 config.toml 文件!**
|
||||
>
|
||||
> 让系统根据你的 `config_schema` 自动生成配置文件,这是插件系统的核心设计原则。
|
||||
|
||||
不需要继承`BasePlugin`,不需要复杂的配置传递,不需要手动创建配置文件,组件内置的`get_config`方法和自动化的配置生成机制已经为你准备好了一切!
|
||||
@@ -1,20 +1,14 @@
|
||||
# 🚀 快速开始指南
|
||||
|
||||
本指南将带你用5分钟时间,从零开始创建一个功能完整的MaiCore插件。
|
||||
本指南将带你从零开始创建一个功能完整的MaiCore插件。
|
||||
|
||||
## 📖 概述
|
||||
|
||||
这个指南将带你快速创建你的第一个MaiCore插件。我们将创建一个简单的问候插件,展示插件系统的基本概念。无需阅读其他文档,跟着本指南就能完成!
|
||||
这个指南将带你快速创建你的第一个MaiCore插件。我们将创建一个简单的问候插件,展示插件系统的基本概念。
|
||||
|
||||
## 🎯 学习目标
|
||||
以下代码都在我们的`plugins/hello_world_plugin/`目录下。
|
||||
|
||||
- 理解插件的基本结构
|
||||
- 从最简单的插件开始,循序渐进
|
||||
- 学会创建Action组件(智能动作)
|
||||
- 学会创建Command组件(命令响应)
|
||||
- 掌握配置Schema定义和配置文件自动生成(可选)
|
||||
|
||||
## 📂 准备工作
|
||||
### 📂 准备工作
|
||||
|
||||
确保你已经:
|
||||
|
||||
@@ -26,16 +20,29 @@
|
||||
|
||||
### 1. 创建插件目录
|
||||
|
||||
在项目根目录的 `plugins/` 文件夹下创建你的插件目录,目录名与插件名保持一致:
|
||||
在项目根目录的 `plugins/` 文件夹下创建你的插件目录
|
||||
|
||||
可以用以下命令快速创建:
|
||||
这里我们创建一个名为 `hello_world_plugin` 的目录
|
||||
|
||||
```bash
|
||||
mkdir plugins/hello_world_plugin
|
||||
cd plugins/hello_world_plugin
|
||||
### 2. 创建`_manifest.json`文件
|
||||
|
||||
在插件目录下面创建一个 `_manifest.json` 文件,内容如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "Hello World 插件",
|
||||
"version": "1.0.0",
|
||||
"description": "一个简单的 Hello World 插件",
|
||||
"author": {
|
||||
"name": "你的名字"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 创建最简单的插件
|
||||
有关 `_manifest.json` 的详细说明,请参考 [Manifest文件指南](./manifest-guide.md)。
|
||||
|
||||
### 3. 创建最简单的插件
|
||||
|
||||
让我们从最基础的开始!创建 `plugin.py` 文件:
|
||||
|
||||
@@ -43,34 +50,33 @@ cd plugins/hello_world_plugin
|
||||
from typing import List, Tuple, Type
|
||||
from src.plugin_system import BasePlugin, register_plugin, ComponentInfo
|
||||
|
||||
# ===== 插件注册 =====
|
||||
|
||||
@register_plugin
|
||||
@register_plugin # 注册插件
|
||||
class HelloWorldPlugin(BasePlugin):
|
||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||
|
||||
# 插件基本信息(必须填写)
|
||||
# 以下是插件基本信息和方法(必须填写)
|
||||
plugin_name = "hello_world_plugin"
|
||||
plugin_description = "我的第一个MaiCore插件"
|
||||
plugin_version = "1.0.0"
|
||||
plugin_author = "你的名字"
|
||||
enable_plugin = True # 启用插件
|
||||
dependencies = [] # 插件依赖列表(目前为空)
|
||||
python_dependencies = [] # Python依赖列表(目前为空)
|
||||
config_file_name = "config.toml" # 配置文件名
|
||||
config_schema = {} # 配置文件模式(目前为空)
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: # 获取插件组件
|
||||
"""返回插件包含的组件列表(目前是空的)"""
|
||||
return []
|
||||
```
|
||||
|
||||
🎉 **恭喜!你刚刚创建了一个最简单但完整的MaiCore插件!**
|
||||
🎉 恭喜!你刚刚创建了一个最简单但完整的MaiCore插件!
|
||||
|
||||
**解释一下这些代码:**
|
||||
|
||||
- 首先,我们在plugin.py中定义了一个HelloWorldPulgin插件类,继承自 `BasePlugin` ,提供基本功能。
|
||||
- 首先,我们在`plugin.py`中定义了一个HelloWorldPlugin插件类,继承自 `BasePlugin` ,提供基本功能。
|
||||
- 通过给类加上,`@register_plugin` 装饰器,我们告诉系统"这是一个插件"
|
||||
- `plugin_name` 等是插件的基本信息,必须填写,**此部分必须与目录名称相同,否则插件无法使用**
|
||||
- `get_plugin_components()` 返回插件的功能组件,现在我们没有定义任何action(动作)或者command(指令),是空的
|
||||
- `plugin_name` 等是插件的基本信息,必须填写
|
||||
- `get_plugin_components()` 返回插件的功能组件,现在我们没有定义任何 Action, Command 或者 EventHandler,所以返回空列表。
|
||||
|
||||
### 3. 测试基础插件
|
||||
### 4. 测试基础插件
|
||||
|
||||
现在就可以测试这个插件了!启动MaiCore:
|
||||
|
||||
@@ -80,7 +86,7 @@ class HelloWorldPlugin(BasePlugin):
|
||||
|
||||

|
||||
|
||||
### 4. 添加第一个功能:问候Action
|
||||
### 5. 添加第一个功能:问候Action
|
||||
|
||||
现在我们要给插件加入一个有用的功能,我们从最好玩的Action做起
|
||||
|
||||
@@ -107,40 +113,34 @@ class HelloAction(BaseAction):
|
||||
# === 基本信息(必须填写)===
|
||||
action_name = "hello_greeting"
|
||||
action_description = "向用户发送问候消息"
|
||||
activation_type = ActionActivationType.ALWAYS # 始终激活
|
||||
|
||||
# === 功能描述(必须填写)===
|
||||
action_parameters = {
|
||||
"greeting_message": "要发送的问候消息"
|
||||
}
|
||||
action_require = [
|
||||
"需要发送友好问候时使用",
|
||||
"当有人向你问好时使用",
|
||||
"当你遇见没有见过的人时使用"
|
||||
]
|
||||
action_parameters = {"greeting_message": "要发送的问候消息"}
|
||||
action_require = ["需要发送友好问候时使用", "当有人向你问好时使用", "当你遇见没有见过的人时使用"]
|
||||
associated_types = ["text"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
"""执行问候动作 - 这是核心功能"""
|
||||
# 发送问候消息
|
||||
greeting_message = self.action_data.get("greeting_message", "")
|
||||
|
||||
message = "嗨!很开心见到你!😊" + greeting_message
|
||||
base_message = self.get_config("greeting.message", "嗨!很开心见到你!😊")
|
||||
message = base_message + greeting_message
|
||||
await self.send_text(message)
|
||||
|
||||
return True, "发送了问候消息"
|
||||
|
||||
# ===== 插件注册 =====
|
||||
|
||||
@register_plugin
|
||||
class HelloWorldPlugin(BasePlugin):
|
||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||
|
||||
# 插件基本信息
|
||||
plugin_name = "hello_world_plugin"
|
||||
plugin_description = "我的第一个MaiCore插件,包含问候功能"
|
||||
plugin_version = "1.0.0"
|
||||
plugin_author = "你的名字"
|
||||
enable_plugin = True
|
||||
dependencies = []
|
||||
python_dependencies = []
|
||||
config_file_name = "config.toml"
|
||||
config_schema = {}
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
"""返回插件包含的组件列表"""
|
||||
@@ -150,13 +150,17 @@ class HelloWorldPlugin(BasePlugin):
|
||||
]
|
||||
```
|
||||
|
||||
**新增内容解释:**
|
||||
**解释一下这些代码:**
|
||||
|
||||
- `HelloAction` 是一个Action组件,MaiCore可能会选择使用它
|
||||
- `HelloAction` 是我们定义的问候动作类,继承自 `BaseAction`,并实现了核心功能。
|
||||
- 在 `HelloWorldPlugin` 中,我们通过 `get_plugin_components()` 方法,通过调用`get_action_info()`这个内置方法将 `HelloAction` 注册为插件的一个组件。
|
||||
- 这样一来,当插件被加载时,问候动作也会被一并加载,并可以在MaiCore中使用。
|
||||
- `execute()` 函数是Action的核心,定义了当Action被MaiCore选择后,具体要做什么
|
||||
- `self.send_text()` 是发送文本消息的便捷方法
|
||||
|
||||
### 5. 测试问候功能
|
||||
Action 组件中有关`activation_type`、`action_parameters`、`action_require`、`associated_types` 等的详细说明请参考 [Action组件指南](./action-components.md)。
|
||||
|
||||
### 6. 测试问候Action
|
||||
|
||||
重启MaiCore,然后在聊天中发送任意消息,比如:
|
||||
|
||||
@@ -174,96 +178,17 @@ MaiCore可能会选择使用你的问候Action,发送回复:
|
||||
|
||||
> **💡 小提示**:MaiCore会智能地决定什么时候使用它。如果没有立即看到效果,多试几次不同的消息。
|
||||
|
||||
🎉 **太棒了!你的插件已经有实际功能了!**
|
||||
🎉 太棒了!你的插件已经有实际功能了!
|
||||
|
||||
### 5.5. 了解激活系统(重要概念)
|
||||
|
||||
Action固然好用简单,但是现在有个问题,当用户加载了非常多的插件,添加了很多自定义Action,LLM需要选择的Action也会变多
|
||||
|
||||
而不断增多的Action会加大LLM的消耗和负担,降低Action使用的精准度。而且我们并不需要LLM在所有时候都考虑所有Action
|
||||
|
||||
例如,当群友只是在进行正常的聊天,就没有必要每次都考虑是否要选择“禁言”动作,这不仅影响决策速度,还会增加消耗。
|
||||
|
||||
那有什么办法,能够让Action有选择的加入MaiCore的决策池呢?
|
||||
|
||||
**什么是激活系统?**
|
||||
激活系统决定了什么时候你的Action会被MaiCore"考虑"使用:
|
||||
|
||||
- **`ActionActivationType.ALWAYS`** - 总是可用(默认值)
|
||||
- **`ActionActivationType.KEYWORD`** - 只有消息包含特定关键词时才可用
|
||||
- **`ActionActivationType.PROBABILITY`** - 根据概率随机可用
|
||||
- **`ActionActivationType.NEVER`** - 永不可用(用于调试)
|
||||
|
||||
> **💡 使用提示**:
|
||||
>
|
||||
> - 推荐使用枚举类型(如 `ActionActivationType.ALWAYS`),有代码提示和类型检查
|
||||
> - 也可以直接使用字符串(如 `"always"`),系统都支持
|
||||
|
||||
### 5.6. 进阶:尝试关键词激活(可选)
|
||||
|
||||
现在让我们尝试一个更精确的激活方式!添加一个只在用户说特定关键词时才激活的Action:
|
||||
|
||||
```python
|
||||
# 在HelloAction后面添加这个新Action
|
||||
class ByeAction(BaseAction):
|
||||
"""告别Action - 只在用户说再见时激活"""
|
||||
|
||||
action_name = "bye_greeting"
|
||||
action_description = "向用户发送告别消息"
|
||||
|
||||
# 使用关键词激活
|
||||
focus_activation_type = ActionActivationType.KEYWORD
|
||||
normal_activation_type = ActionActivationType.KEYWORD
|
||||
|
||||
# 关键词设置
|
||||
activation_keywords = ["再见", "bye", "88", "拜拜"]
|
||||
keyword_case_sensitive = False
|
||||
|
||||
action_parameters = {"bye_message": "要发送的告别消息"}
|
||||
action_require = [
|
||||
"用户要告别时使用",
|
||||
"当有人要离开时使用",
|
||||
"当有人和你说再见时使用",
|
||||
]
|
||||
associated_types = ["text"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
bye_message = self.action_data.get("bye_message","")
|
||||
|
||||
message = "再见!期待下次聊天!👋" + bye_message
|
||||
await self.send_text(message)
|
||||
return True, "发送了告别消息"
|
||||
```
|
||||
|
||||
然后在插件注册中添加这个Action:
|
||||
|
||||
```python
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
return [
|
||||
(HelloAction.get_action_info(), HelloAction),
|
||||
(ByeAction.get_action_info(), ByeAction), # 添加告别Action
|
||||
]
|
||||
```
|
||||
|
||||
现在测试:发送"再见",应该会触发告别Action!
|
||||
|
||||
**关键词激活的特点:**
|
||||
|
||||
- 更精确:只在包含特定关键词时才会被考虑
|
||||
- 更可预测:用户知道说什么会触发什么功能
|
||||
- 更适合:特定场景或命令式的功能
|
||||
|
||||
### 6. 添加第二个功能:时间查询Command
|
||||
### 7. 添加第二个功能:时间查询Command
|
||||
|
||||
现在让我们添加一个Command组件。Command和Action不同,它是直接响应用户命令的:
|
||||
|
||||
Command是最简单,最直接的相应,不由LLM判断选择使用
|
||||
Command是最简单,最直接的响应,不由LLM判断选择使用
|
||||
|
||||
```python
|
||||
# 在现有代码基础上,添加Command组件
|
||||
|
||||
# ===== Command组件 =====
|
||||
|
||||
import datetime
|
||||
from src.plugin_system import BaseCommand
|
||||
#导入Command基类
|
||||
|
||||
@@ -275,16 +200,11 @@ class TimeCommand(BaseCommand):
|
||||
|
||||
# === 命令设置(必须填写)===
|
||||
command_pattern = r"^/time$" # 精确匹配 "/time" 命令
|
||||
command_help = "查询当前时间"
|
||||
command_examples = ["/time"]
|
||||
intercept_message = True # 拦截消息,不让其他组件处理
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
async def execute(self) -> Tuple[bool, Optional[str], bool]:
|
||||
"""执行时间查询"""
|
||||
import datetime
|
||||
|
||||
# 获取当前时间
|
||||
time_format = self.get_config("time.format", "%Y-%m-%d %H:%M:%S")
|
||||
time_format: str = "%Y-%m-%d %H:%M:%S"
|
||||
now = datetime.datetime.now()
|
||||
time_str = now.strftime(time_format)
|
||||
|
||||
@@ -292,36 +212,37 @@ class TimeCommand(BaseCommand):
|
||||
message = f"⏰ 当前时间:{time_str}"
|
||||
await self.send_text(message)
|
||||
|
||||
return True, f"显示了当前时间: {time_str}"
|
||||
|
||||
# ===== 插件注册 =====
|
||||
return True, f"显示了当前时间: {time_str}", True
|
||||
|
||||
@register_plugin
|
||||
class HelloWorldPlugin(BasePlugin):
|
||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||
|
||||
# 插件基本信息
|
||||
plugin_name = "hello_world_plugin"
|
||||
plugin_description = "我的第一个MaiCore插件,包含问候和时间查询功能"
|
||||
plugin_version = "1.0.0"
|
||||
plugin_author = "你的名字"
|
||||
enable_plugin = True
|
||||
dependencies = []
|
||||
python_dependencies = []
|
||||
config_file_name = "config.toml"
|
||||
config_schema = {}
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
return [
|
||||
(HelloAction.get_action_info(), HelloAction),
|
||||
(ByeAction.get_action_info(), ByeAction),
|
||||
(TimeCommand.get_command_info(), TimeCommand),
|
||||
]
|
||||
```
|
||||
|
||||
同样的,我们通过 `get_plugin_components()` 方法,通过调用`get_action_info()`这个内置方法将 `TimeCommand` 注册为插件的一个组件。
|
||||
|
||||
**Command组件解释:**
|
||||
|
||||
- Command是直接响应用户命令的组件
|
||||
- `command_pattern` 使用正则表达式匹配用户输入
|
||||
- `^/time$` 表示精确匹配 "/time"
|
||||
- `intercept_message = True` 表示处理完命令后不再让其他组件处理
|
||||
|
||||
### 7. 测试时间查询功能
|
||||
有关 Command 组件的更多信息,请参考 [Command组件指南](./command-components.md)。
|
||||
|
||||
### 8. 测试时间查询Command
|
||||
|
||||
重启MaiCore,发送命令:
|
||||
|
||||
@@ -332,106 +253,147 @@ class HelloWorldPlugin(BasePlugin):
|
||||
你应该会收到回复:
|
||||
|
||||
```
|
||||
⏰ 当前时间:2024-01-01 12:30:45
|
||||
⏰ 当前时间:2024-01-01 12:00:00
|
||||
```
|
||||
|
||||
🎉 **太棒了!现在你的插件有3个功能了!**
|
||||
🎉 太棒了!现在你已经了解了基本的 Action 和 Command 组件的使用方法。你可以根据自己的需求,继续扩展插件的功能,添加更多的 Action 和 Command 组件,让你的插件更加丰富和强大!
|
||||
|
||||
### 8. 添加配置文件(可选进阶)
|
||||
---
|
||||
|
||||
如果你想让插件更加灵活,可以添加配置支持。
|
||||
## 进阶教程
|
||||
|
||||
如果你想让插件更加灵活和强大,可以参考接下来的进阶教程。
|
||||
|
||||
### 1. 添加配置文件
|
||||
|
||||
想要为插件添加配置文件吗?让我们一起来配置`config_schema`属性!
|
||||
|
||||
> **🚨 重要:不要手动创建config.toml文件!**
|
||||
>
|
||||
> 我们需要在插件代码中定义配置Schema,让系统自动生成配置文件。
|
||||
|
||||
#### 📄 配置架构说明
|
||||
|
||||
在新的插件系统中,我们采用了**职责分离**的设计:
|
||||
|
||||
- **`_manifest.json`** - 插件元数据(名称、版本、描述、作者等)
|
||||
- **`config.toml`** - 运行时配置(启用状态、功能参数等)
|
||||
|
||||
这样避免了信息重复,提高了维护性。
|
||||
|
||||
首先,在插件类中定义配置Schema:
|
||||
|
||||
```python
|
||||
from src.plugin_system.base.config_types import ConfigField
|
||||
from src.plugin_system import ConfigField
|
||||
|
||||
@register_plugin
|
||||
class HelloWorldPlugin(BasePlugin):
|
||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||
|
||||
plugin_name = "hello_world_plugin"
|
||||
plugin_description = "我的第一个MaiCore插件,包含问候和时间查询功能"
|
||||
plugin_version = "1.0.0"
|
||||
plugin_author = "你的名字"
|
||||
enable_plugin = True
|
||||
config_file_name = "config.toml" # 配置文件名
|
||||
|
||||
# 配置节描述
|
||||
config_section_descriptions = {
|
||||
"plugin": "插件启用配置",
|
||||
"greeting": "问候功能配置",
|
||||
"time": "时间查询配置"
|
||||
}
|
||||
# 插件基本信息
|
||||
plugin_name: str = "hello_world_plugin" # 内部标识符
|
||||
enable_plugin: bool = True
|
||||
dependencies: List[str] = [] # 插件依赖列表
|
||||
python_dependencies: List[str] = [] # Python包依赖列表
|
||||
config_file_name: str = "config.toml" # 配置文件名
|
||||
|
||||
# 配置Schema定义
|
||||
config_schema = {
|
||||
config_schema: dict = {
|
||||
"plugin": {
|
||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件")
|
||||
"name": ConfigField(type=str, default="hello_world_plugin", description="插件名称"),
|
||||
"version": ConfigField(type=str, default="1.0.0", description="插件版本"),
|
||||
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
||||
},
|
||||
"greeting": {
|
||||
"message": ConfigField(
|
||||
type=str,
|
||||
default="嗨!很开心见到你!😊",
|
||||
description="默认问候消息"
|
||||
),
|
||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号")
|
||||
"message": ConfigField(type=str, default="嗨!很开心见到你!😊", description="默认问候消息"),
|
||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号"),
|
||||
},
|
||||
"time": {
|
||||
"format": ConfigField(
|
||||
type=str,
|
||||
default="%Y-%m-%d %H:%M:%S",
|
||||
description="时间显示格式"
|
||||
)
|
||||
}
|
||||
"time": {"format": ConfigField(type=str, default="%Y-%m-%d %H:%M:%S", description="时间显示格式")},
|
||||
}
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
return [
|
||||
(HelloAction.get_action_info(), HelloAction),
|
||||
(ByeAction.get_action_info(), ByeAction),
|
||||
(TimeCommand.get_command_info(), TimeCommand),
|
||||
]
|
||||
```
|
||||
|
||||
然后修改Action和Command代码,让它们读取配置:
|
||||
这会生成一个如下的 `config.toml` 文件:
|
||||
|
||||
```toml
|
||||
# hello_world_plugin - 自动生成的配置文件
|
||||
# 我的第一个MaiCore插件,包含问候功能和时间查询等基础示例
|
||||
|
||||
# 插件基本信息
|
||||
[plugin]
|
||||
|
||||
# 插件名称
|
||||
name = "hello_world_plugin"
|
||||
|
||||
# 插件版本
|
||||
version = "1.0.0"
|
||||
|
||||
# 是否启用插件
|
||||
enabled = false
|
||||
|
||||
|
||||
# 问候功能配置
|
||||
[greeting]
|
||||
|
||||
# 默认问候消息
|
||||
message = "嗨!很开心见到你!😊"
|
||||
|
||||
# 是否启用表情符号
|
||||
enable_emoji = true
|
||||
|
||||
|
||||
# 时间查询配置
|
||||
[time]
|
||||
|
||||
# 时间显示格式
|
||||
format = "%Y-%m-%d %H:%M:%S"
|
||||
```
|
||||
|
||||
然后修改Action和Command代码,通过 `get_config()` 方法让它们读取配置(配置的键是命名空间式的):
|
||||
|
||||
```python
|
||||
# 在HelloAction的execute方法中:
|
||||
class HelloAction(BaseAction):
|
||||
"""问候Action - 简单的问候动作"""
|
||||
|
||||
# === 基本信息(必须填写)===
|
||||
action_name = "hello_greeting"
|
||||
action_description = "向用户发送问候消息"
|
||||
activation_type = ActionActivationType.ALWAYS # 始终激活
|
||||
|
||||
# === 功能描述(必须填写)===
|
||||
action_parameters = {"greeting_message": "要发送的问候消息"}
|
||||
action_require = ["需要发送友好问候时使用", "当有人向你问好时使用", "当你遇见没有见过的人时使用"]
|
||||
associated_types = ["text"]
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 从配置文件读取问候消息
|
||||
"""执行问候动作 - 这是核心功能"""
|
||||
# 发送问候消息
|
||||
greeting_message = self.action_data.get("greeting_message", "")
|
||||
base_message = self.get_config("greeting.message", "嗨!很开心见到你!😊")
|
||||
|
||||
message = base_message + greeting_message
|
||||
await self.send_text(message)
|
||||
|
||||
return True, "发送了问候消息"
|
||||
|
||||
# 在TimeCommand的execute方法中:
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
class TimeCommand(BaseCommand):
|
||||
"""时间查询Command - 响应/time命令"""
|
||||
|
||||
command_name = "time"
|
||||
command_description = "查询当前时间"
|
||||
|
||||
# === 命令设置(必须填写)===
|
||||
command_pattern = r"^/time$" # 精确匹配 "/time" 命令
|
||||
|
||||
async def execute(self) -> Tuple[bool, str, bool]:
|
||||
"""执行时间查询"""
|
||||
import datetime
|
||||
|
||||
# 从配置文件读取时间格式
|
||||
time_format = self.get_config("time.format", "%Y-%m-%d %H:%M:%S")
|
||||
# 获取当前时间
|
||||
time_format: str = self.get_config("time.format", "%Y-%m-%d %H:%M:%S") # type: ignore
|
||||
now = datetime.datetime.now()
|
||||
time_str = now.strftime(time_format)
|
||||
|
||||
# 发送时间信息
|
||||
message = f"⏰ 当前时间:{time_str}"
|
||||
await self.send_text(message)
|
||||
return True, f"显示了当前时间: {time_str}"
|
||||
|
||||
return True, f"显示了当前时间: {time_str}", True
|
||||
```
|
||||
|
||||
**配置系统工作流程:**
|
||||
@@ -441,47 +403,20 @@ async def execute(self) -> Tuple[bool, str]:
|
||||
3. **用户修改**: 用户可以修改生成的配置文件
|
||||
4. **代码读取**: 使用 `self.get_config()` 读取配置值
|
||||
|
||||
**配置功能解释:**
|
||||
**绝对不要手动创建 `config.toml` 文件!**
|
||||
|
||||
- `self.get_config()` 可以读取配置文件中的值
|
||||
- 第一个参数是配置路径(用点分隔),第二个参数是默认值
|
||||
- 配置文件会包含详细的注释和说明,用户可以轻松理解和修改
|
||||
- **绝不要手动创建配置文件**,让系统自动生成
|
||||
更详细的配置系统介绍请参考 [配置指南](./configuration-guide.md)。
|
||||
|
||||
### 9. 创建说明文档(可选)
|
||||
### 2. 创建说明文档
|
||||
|
||||
创建 `README.md` 文件来说明你的插件:
|
||||
你可以创建一个 `README.md` 文件,描述插件的功能和使用方法。
|
||||
|
||||
```markdown
|
||||
# Hello World 插件
|
||||
### 3. 发布到插件市场
|
||||
|
||||
## 概述
|
||||
我的第一个MaiCore插件,包含问候和时间查询功能。
|
||||
如果你想让更多人使用你的插件,可以将它发布到MaiCore的插件市场。
|
||||
|
||||
## 功能
|
||||
- **问候功能**: 当用户说"你好"、"hello"、"hi"时自动回复
|
||||
- **时间查询**: 发送 `/time` 命令查询当前时间
|
||||
这部分请参考 [plugin-repo](https://github.com/Maim-with-u/plugin-repo) 的文档。
|
||||
|
||||
## 使用方法
|
||||
### 问候功能
|
||||
发送包含以下关键词的消息:
|
||||
- "你好"
|
||||
- "hello"
|
||||
- "hi"
|
||||
---
|
||||
|
||||
### 时间查询
|
||||
发送命令:`/time`
|
||||
|
||||
## 配置文件
|
||||
插件会自动生成 `config.toml` 配置文件,用户可以修改:
|
||||
- 问候消息内容
|
||||
- 时间显示格式
|
||||
- 插件启用状态
|
||||
|
||||
注意:配置文件是自动生成的,不要手动创建!
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
🎉 恭喜你!你已经成功的创建了自己的插件了!
|
||||
|
||||
@@ -12,7 +12,7 @@ from src.chat.message_receive.storage import MessageStorage
|
||||
from src.chat.heart_flow.heartflow import heartflow
|
||||
from src.chat.utils.utils import is_mentioned_bot_in_message
|
||||
from src.chat.utils.timer_calculator import Timer
|
||||
from src.chat.utils.chat_message_builder import replace_user_references_in_content
|
||||
from src.chat.utils.chat_message_builder import replace_user_references_sync
|
||||
from src.common.logger import get_logger
|
||||
from src.person_info.relationship_manager import get_relationship_manager
|
||||
from src.mood.mood_manager import mood_manager
|
||||
@@ -151,10 +151,9 @@ class HeartFCMessageReceiver:
|
||||
processed_plain_text = re.sub(picid_pattern, "[图片]", message.processed_plain_text)
|
||||
|
||||
# 应用用户引用格式替换,将回复<aaa:bbb>和@<aaa:bbb>格式转换为可读格式
|
||||
processed_plain_text = replace_user_references_in_content(
|
||||
processed_plain_text = replace_user_references_sync(
|
||||
processed_plain_text,
|
||||
message.message_info.platform,
|
||||
is_async=False,
|
||||
message.message_info.platform, # type: ignore
|
||||
replace_bot_name=True
|
||||
)
|
||||
|
||||
|
||||
@@ -225,6 +225,7 @@ class Hippocampus:
|
||||
|
||||
@staticmethod
|
||||
def find_topic_llm(text: str, topic_num: int | list[int]):
|
||||
# sourcery skip: inline-immediately-returned-variable
|
||||
topic_num_str = ""
|
||||
if isinstance(topic_num, list):
|
||||
topic_num_str = f"{topic_num[0]}-{topic_num[1]}"
|
||||
@@ -319,7 +320,7 @@ class Hippocampus:
|
||||
|
||||
# 使用LLM提取关键词 - 根据详细文本长度分布优化topic_num计算
|
||||
text_length = len(text)
|
||||
topic_num:str|list[int] = None
|
||||
topic_num: int | list[int] = 0
|
||||
if text_length <= 5:
|
||||
words = jieba.cut(text)
|
||||
keywords = [word for word in words if len(word) > 1]
|
||||
@@ -338,7 +339,6 @@ class Hippocampus:
|
||||
else:
|
||||
topic_num = 5 # 51+字符: 5个关键词 (其余长文本)
|
||||
|
||||
|
||||
topics_response, (reasoning_content, model_name) = await self.model_summary.generate_response_async(
|
||||
self.find_topic_llm(text, topic_num)
|
||||
)
|
||||
@@ -1312,6 +1312,7 @@ class ParahippocampalGyrus:
|
||||
return compressed_memory, similar_topics_dict
|
||||
|
||||
async def operation_build_memory(self):
|
||||
# sourcery skip: merge-list-appends-into-extend
|
||||
logger.info("------------------------------------开始构建记忆--------------------------------------")
|
||||
start_time = time.time()
|
||||
memory_samples = self.hippocampus.entorhinal_cortex.get_memory_sample()
|
||||
|
||||
@@ -17,7 +17,11 @@ from src.chat.message_receive.uni_message_sender import HeartFCSender
|
||||
from src.chat.utils.timer_calculator import Timer # <--- Import Timer
|
||||
from src.chat.utils.utils import get_chat_type_and_target_info
|
||||
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
||||
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat, replace_user_references_in_content
|
||||
from src.chat.utils.chat_message_builder import (
|
||||
build_readable_messages,
|
||||
get_raw_msg_before_timestamp_with_chat,
|
||||
replace_user_references_sync,
|
||||
)
|
||||
from src.chat.express.expression_selector import expression_selector
|
||||
from src.chat.knowledge.knowledge_lib import qa_manager
|
||||
from src.chat.memory_system.memory_activator import MemoryActivator
|
||||
@@ -30,6 +34,7 @@ from src.plugin_system.base.component_types import ActionInfo
|
||||
|
||||
logger = get_logger("replyer")
|
||||
|
||||
|
||||
def init_prompt():
|
||||
Prompt("你正在qq群里聊天,下面是群里在聊的内容:", "chat_target_group1")
|
||||
Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1")
|
||||
@@ -356,10 +361,14 @@ class DefaultReplyer:
|
||||
expression_habits_block = ""
|
||||
expression_habits_title = ""
|
||||
if style_habits_str.strip():
|
||||
expression_habits_title = "你可以参考以下的语言习惯,当情景合适就使用,但不要生硬使用,以合理的方式结合到你的回复中:"
|
||||
expression_habits_title = (
|
||||
"你可以参考以下的语言习惯,当情景合适就使用,但不要生硬使用,以合理的方式结合到你的回复中:"
|
||||
)
|
||||
expression_habits_block += f"{style_habits_str}\n"
|
||||
if grammar_habits_str.strip():
|
||||
expression_habits_title = "你可以选择下面的句法进行回复,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式使用:"
|
||||
expression_habits_title = (
|
||||
"你可以选择下面的句法进行回复,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式使用:"
|
||||
)
|
||||
expression_habits_block += f"{grammar_habits_str}\n"
|
||||
|
||||
if style_habits_str.strip() and grammar_habits_str.strip():
|
||||
@@ -367,7 +376,6 @@ class DefaultReplyer:
|
||||
|
||||
expression_habits_block = f"{expression_habits_title}\n{expression_habits_block}"
|
||||
|
||||
|
||||
return expression_habits_block
|
||||
|
||||
async def build_memory_block(self, chat_history, target):
|
||||
@@ -630,13 +638,7 @@ class DefaultReplyer:
|
||||
|
||||
sender, target = self._parse_reply_target(reply_to)
|
||||
|
||||
target = replace_user_references_in_content(
|
||||
target,
|
||||
chat_stream.platform,
|
||||
is_async=False,
|
||||
replace_bot_name=True
|
||||
)
|
||||
|
||||
target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True)
|
||||
|
||||
# 构建action描述 (如果启用planner)
|
||||
action_descriptions = ""
|
||||
@@ -687,16 +689,12 @@ class DefaultReplyer:
|
||||
self._time_and_run_task(
|
||||
self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits"
|
||||
),
|
||||
self._time_and_run_task(
|
||||
self.build_relation_info(reply_data), "relation_info"
|
||||
),
|
||||
self._time_and_run_task(self.build_relation_info(reply_data), "relation_info"),
|
||||
self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"),
|
||||
self._time_and_run_task(
|
||||
self.build_tool_info(chat_talking_prompt_short, reply_data, enable_tool=enable_tool), "tool_info"
|
||||
),
|
||||
self._time_and_run_task(
|
||||
get_prompt_info(target, threshold=0.38), "prompt_info"
|
||||
),
|
||||
self._time_and_run_task(get_prompt_info(target, threshold=0.38), "prompt_info"),
|
||||
)
|
||||
|
||||
# 任务名称中英文映射
|
||||
@@ -705,7 +703,7 @@ class DefaultReplyer:
|
||||
"relation_info": "感受关系",
|
||||
"memory_block": "回忆",
|
||||
"tool_info": "使用工具",
|
||||
"prompt_info": "获取知识"
|
||||
"prompt_info": "获取知识",
|
||||
}
|
||||
|
||||
# 处理结果
|
||||
@@ -815,10 +813,9 @@ class DefaultReplyer:
|
||||
--------------------------------
|
||||
{time_block}
|
||||
这是你和{sender}的对话,你们正在交流中:
|
||||
{core_dialogue_prompt}"""
|
||||
{core_dialogue_prompt}""",
|
||||
)
|
||||
|
||||
|
||||
# 使用 s4u 风格的模板
|
||||
template_name = "s4u_style_prompt"
|
||||
|
||||
@@ -855,7 +852,7 @@ class DefaultReplyer:
|
||||
identity_block=identity_block,
|
||||
sender=sender,
|
||||
target=target,
|
||||
chat_info=chat_talking_prompt
|
||||
chat_info=chat_talking_prompt,
|
||||
)
|
||||
|
||||
# 使用原有的模式
|
||||
@@ -1081,7 +1078,9 @@ async def get_prompt_info(message: str, threshold: float):
|
||||
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
|
||||
|
||||
# 格式化知识信息
|
||||
formatted_prompt_info = await global_prompt_manager.format_prompt("knowledge_prompt", prompt_info=related_info)
|
||||
formatted_prompt_info = await global_prompt_manager.format_prompt(
|
||||
"knowledge_prompt", prompt_info=related_info
|
||||
)
|
||||
return formatted_prompt_info
|
||||
else:
|
||||
logger.debug("从LPMM知识库获取知识失败,可能是从未导入过知识,返回空知识...")
|
||||
|
||||
@@ -2,7 +2,7 @@ import time # 导入 time 模块以获取当前时间
|
||||
import random
|
||||
import re
|
||||
|
||||
from typing import List, Dict, Any, Tuple, Optional, Union, Callable
|
||||
from typing import List, Dict, Any, Tuple, Optional, Callable
|
||||
from rich.traceback import install
|
||||
|
||||
from src.config.config import global_config
|
||||
@@ -15,13 +15,12 @@ from src.chat.utils.utils import translate_timestamp_to_human_readable,assign_me
|
||||
install(extra_lines=3)
|
||||
|
||||
|
||||
def replace_user_references_in_content(
|
||||
def replace_user_references_sync(
|
||||
content: str,
|
||||
platform: str,
|
||||
name_resolver: Union[Callable[[str, str], str], Callable[[str, str], Any]] = None,
|
||||
is_async: bool = False,
|
||||
replace_bot_name: bool = True
|
||||
) -> Union[str, Any]:
|
||||
name_resolver: Optional[Callable[[str, str], str]] = None,
|
||||
replace_bot_name: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
替换内容中的用户引用格式,包括回复<aaa:bbb>和@<aaa:bbb>格式
|
||||
|
||||
@@ -30,41 +29,29 @@ def replace_user_references_in_content(
|
||||
platform: 平台标识
|
||||
name_resolver: 名称解析函数,接收(platform, user_id)参数,返回用户名称
|
||||
如果为None,则使用默认的person_info_manager
|
||||
is_async: 是否为异步模式
|
||||
replace_bot_name: 是否将机器人的user_id替换为"机器人昵称(你)"
|
||||
|
||||
Returns:
|
||||
处理后的内容字符串(同步模式)或awaitable对象(异步模式)
|
||||
str: 处理后的内容字符串
|
||||
"""
|
||||
if is_async:
|
||||
return _replace_user_references_async(content, platform, name_resolver, replace_bot_name)
|
||||
else:
|
||||
return _replace_user_references_sync(content, platform, name_resolver, replace_bot_name)
|
||||
|
||||
|
||||
def _replace_user_references_sync(
|
||||
content: str,
|
||||
platform: str,
|
||||
name_resolver: Optional[Callable[[str, str], str]] = None,
|
||||
replace_bot_name: bool = True
|
||||
) -> str:
|
||||
"""同步版本的用户引用替换"""
|
||||
if name_resolver is None:
|
||||
person_info_manager = get_person_info_manager()
|
||||
|
||||
def default_resolver(platform: str, user_id: str) -> str:
|
||||
# 检查是否是机器人自己
|
||||
if replace_bot_name and user_id == global_config.bot.qq_account:
|
||||
return f"{global_config.bot.nickname}(你)"
|
||||
person_id = PersonInfoManager.get_person_id(platform, user_id)
|
||||
return person_info_manager.get_value_sync(person_id, "person_name") or user_id
|
||||
return person_info_manager.get_value_sync(person_id, "person_name") or user_id # type: ignore
|
||||
|
||||
name_resolver = default_resolver
|
||||
|
||||
# 处理回复<aaa:bbb>格式
|
||||
reply_pattern = r"回复<([^:<>]+):([^:<>]+)>"
|
||||
match = re.search(reply_pattern, content)
|
||||
if match:
|
||||
aaa = match.group(1)
|
||||
bbb = match.group(2)
|
||||
aaa = match[1]
|
||||
bbb = match[2]
|
||||
try:
|
||||
# 检查是否是机器人自己
|
||||
if replace_bot_name and bbb == global_config.bot.qq_account:
|
||||
@@ -103,21 +90,35 @@ def _replace_user_references_sync(
|
||||
return content
|
||||
|
||||
|
||||
async def _replace_user_references_async(
|
||||
async def replace_user_references_async(
|
||||
content: str,
|
||||
platform: str,
|
||||
name_resolver: Optional[Callable[[str, str], Any]] = None,
|
||||
replace_bot_name: bool = True
|
||||
replace_bot_name: bool = True,
|
||||
) -> str:
|
||||
"""异步版本的用户引用替换"""
|
||||
"""
|
||||
替换内容中的用户引用格式,包括回复<aaa:bbb>和@<aaa:bbb>格式
|
||||
|
||||
Args:
|
||||
content: 要处理的内容字符串
|
||||
platform: 平台标识
|
||||
name_resolver: 名称解析函数,接收(platform, user_id)参数,返回用户名称
|
||||
如果为None,则使用默认的person_info_manager
|
||||
replace_bot_name: 是否将机器人的user_id替换为"机器人昵称(你)"
|
||||
|
||||
Returns:
|
||||
str: 处理后的内容字符串
|
||||
"""
|
||||
if name_resolver is None:
|
||||
person_info_manager = get_person_info_manager()
|
||||
|
||||
async def default_resolver(platform: str, user_id: str) -> str:
|
||||
# 检查是否是机器人自己
|
||||
if replace_bot_name and user_id == global_config.bot.qq_account:
|
||||
return f"{global_config.bot.nickname}(你)"
|
||||
person_id = PersonInfoManager.get_person_id(platform, user_id)
|
||||
return await person_info_manager.get_value(person_id, "person_name") or user_id
|
||||
return await person_info_manager.get_value(person_id, "person_name") or user_id # type: ignore
|
||||
|
||||
name_resolver = default_resolver
|
||||
|
||||
# 处理回复<aaa:bbb>格式
|
||||
@@ -524,7 +525,7 @@ def _build_readable_messages_internal(
|
||||
person_name = "某人"
|
||||
|
||||
# 使用独立函数处理用户引用格式
|
||||
content = replace_user_references_in_content(content, platform, is_async=False, replace_bot_name=replace_bot_name)
|
||||
content = replace_user_references_sync(content, platform, replace_bot_name=replace_bot_name)
|
||||
|
||||
target_str = "这是QQ的一个功能,用于提及某人,但没那么明显"
|
||||
if target_str in content and random.random() < 0.6:
|
||||
@@ -778,6 +779,7 @@ async def build_readable_messages_with_list(
|
||||
|
||||
return formatted_string, details_list
|
||||
|
||||
|
||||
def build_readable_messages_with_id(
|
||||
messages: List[Dict[str, Any]],
|
||||
replace_bot_name: bool = True,
|
||||
@@ -806,9 +808,6 @@ def build_readable_messages_with_id(
|
||||
message_id_list=message_id_list,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
return formatted_string, message_id_list
|
||||
|
||||
|
||||
@@ -894,7 +893,13 @@ def build_readable_messages(
|
||||
if read_mark <= 0:
|
||||
# 没有有效的 read_mark,直接格式化所有消息
|
||||
formatted_string, _, pic_id_mapping, _ = _build_readable_messages_internal(
|
||||
copy_messages, replace_bot_name, merge_messages, timestamp_mode, truncate, show_pic=show_pic, message_id_list=message_id_list
|
||||
copy_messages,
|
||||
replace_bot_name,
|
||||
merge_messages,
|
||||
timestamp_mode,
|
||||
truncate,
|
||||
show_pic=show_pic,
|
||||
message_id_list=message_id_list,
|
||||
)
|
||||
|
||||
# 生成图片映射信息并添加到最前面
|
||||
@@ -1017,7 +1022,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str:
|
||||
|
||||
for msg in messages:
|
||||
try:
|
||||
platform = msg.get("chat_info_platform")
|
||||
platform: str = msg.get("chat_info_platform") # type: ignore
|
||||
user_id = msg.get("user_id")
|
||||
_timestamp = msg.get("time")
|
||||
content: str = ""
|
||||
@@ -1047,7 +1052,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str:
|
||||
except Exception:
|
||||
return "?"
|
||||
|
||||
content = replace_user_references_in_content(content, platform, anon_name_resolver, is_async=False, replace_bot_name=False)
|
||||
content = replace_user_references_sync(content, platform, anon_name_resolver, replace_bot_name=False)
|
||||
|
||||
header = f"{anon_name}说 "
|
||||
output_lines.append(header)
|
||||
|
||||
@@ -36,7 +36,7 @@ def compare_dicts(new, old, path=None, new_comments=None, old_comments=None, log
|
||||
continue
|
||||
if key not in old:
|
||||
comment = get_key_comment(new, key)
|
||||
logs.append(f"新增: {'.'.join(path + [str(key)])} 注释: {comment if comment else '无'}")
|
||||
logs.append(f"新增: {'.'.join(path + [str(key)])} 注释: {comment or '无'}")
|
||||
elif isinstance(new[key], (dict, Table)) and isinstance(old.get(key), (dict, Table)):
|
||||
compare_dicts(new[key], old[key], path + [str(key)], new_comments, old_comments, logs)
|
||||
# 删减项
|
||||
@@ -45,7 +45,7 @@ def compare_dicts(new, old, path=None, new_comments=None, old_comments=None, log
|
||||
continue
|
||||
if key not in new:
|
||||
comment = get_key_comment(old, key)
|
||||
logs.append(f"删减: {'.'.join(path + [str(key)])} 注释: {comment if comment else '无'}")
|
||||
logs.append(f"删减: {'.'.join(path + [str(key)])} 注释: {comment or '无'}")
|
||||
return logs
|
||||
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from src.common.logger import get_logger
|
||||
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||
from src.chat.message_receive.uni_message_sender import HeartFCSender
|
||||
from src.chat.message_receive.message import MessageSending, MessageRecv
|
||||
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, replace_user_references_in_content
|
||||
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, replace_user_references_async
|
||||
from src.person_info.person_info import get_person_info_manager
|
||||
from maim_message import Seg, UserInfo
|
||||
from src.config.config import global_config
|
||||
@@ -183,7 +183,7 @@ async def _find_reply_message(target_stream, reply_to: str) -> Optional[MessageR
|
||||
translate_text = message["processed_plain_text"]
|
||||
|
||||
# 使用独立函数处理用户引用格式
|
||||
translate_text = await replace_user_references_in_content(translate_text, platform, is_async=True)
|
||||
translate_text = await replace_user_references_async(translate_text, platform)
|
||||
|
||||
similarity = difflib.SequenceMatcher(None, text, translate_text).ratio()
|
||||
if similarity >= 0.9:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"license": "GPL-v3.0-or-later",
|
||||
"host_application": {
|
||||
"min_version": "0.9.0"
|
||||
"min_version": "0.9.1"
|
||||
},
|
||||
"homepage_url": "https://github.com/MaiM-with-u/maibot",
|
||||
"repository_url": "https://github.com/MaiM-with-u/maibot",
|
||||
|
||||
@@ -429,6 +429,7 @@ class PluginManagementPlugin(BasePlugin):
|
||||
config_schema: dict = {
|
||||
"plugin": {
|
||||
"enable": ConfigField(bool, default=False, description="是否启用插件"),
|
||||
"config_version": ConfigField(type=str, default="1.0.0", description="配置文件版本"),
|
||||
"permission": ConfigField(list, default=[], description="有权限使用插件管理命令的用户列表"),
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user