插件制作
插件文件放在 live-2d/plugins/community/ 里,应用启动时自动加载已启用的插件。
插件能做三件事:
- 后台运行:定时器、轮询外部数据、监听事件
- 拦截消息:在用户消息发给 AI 之前插手,或者在 AI 回复之后做点什么
- 注册工具:给 AI 提供可以调用的函数(Function Calling)
一个插件可以同时做这三件事。
五分钟上手
新建一个目录:
live-2d/plugins/community/插件名字(英文)/
├── index.js
├── metadata.json
├── plugin_config.json (可选,有配置项才需要)
└── requirements.txt (可选,有 Python 依赖才需要)
metadata.json
{
"name": "my-plugin",
"displayName": "我的插件",
"version": "1.0.0",
"author": "你的名字",
"description": "做什么的",
"main": "index.js"
}
index.js
const { Plugin } = require('../../../js/core/plugin-base.js');
class MyPlugin extends Plugin {
async onStart() {
this.context.log('info', '我启动了');
}
}
module.exports = MyPlugin;
打开应用,在插件标签、社区插件里面能看见你的插件,勾选保存后。启动桌宠
桌宠日志出现 ✅ 插件已加载: my-plugin 就成了。
启用 / 禁用插件
插件的开关由 live-2d/plugins/enabled_plugins.json 统一管理:
{
"plugins": [
"community/my-plugin",
"built-in/auto-chat"
]
}
列表里的插件 = 启用;不在列表里 = 禁用。在 UI 的插件管理页面勾选/取消勾选会自动读写这个文件,不需要手动编辑。
配置项(plugin_config.json)
如果你的插件有可配置的参数,在插件目录下创建 plugin_config.json,使用 schema 格式:
{
"interval": {
"title": "触发间隔(秒)",
"description": "每隔多少秒执行一次",
"type": "int",
"default": 60
},
"keyword": {
"title": "关键词",
"description": "触发条件的关键词",
"type": "string",
"default": "你好"
},
"enabled_feature": {
"title": "启用功能",
"description": "是否开启某个可选功能",
"type": "bool",
"default": true
}
}
支持的字段类型:
| type | 说明 | UI 控件 |
|---|---|---|
string |
单行文本 | 输入框 |
int |
整数 | 输入框(保存时自动校验) |
float |
小数 | 输入框(保存时自动校验) |
bool |
布尔值 | 勾选框 |
text |
多行文本 | 多行输入框 |
object |
嵌套对象 | 分组展示,需配合 fields |
嵌套对象示例:
{
"llm": {
"title": "自定义 LLM",
"description": "留空则使用全局配置",
"type": "object",
"fields": {
"model": {
"title": "模型名称",
"type": "string",
"default": ""
},
"api_key": {
"title": "API Key",
"type": "string",
"default": ""
}
}
}
}
在代码里读取配置:
const cfg = this.context.getPluginConfig();
// cfg 是已解析的平铺对象:{ interval: 60, keyword: "你好", ... }
用户在 UI 里修改配置后,值会写入 plugin_config.json 的 value 字段,下次启动时自动读取。
Python 依赖(requirements.txt)
如果你的插件依赖第三方 Python 包,在插件根目录创建 requirements.txt:
httpx
beautifulsoup4
pytz
当用户从插件广场安装你的插件时,会在 git clone 完成后自动执行:
pip install -r requirements.txt
安装过程中 UI 会显示"正在安装依赖..."的提示。安装失败会弹出错误信息,不会静默跳过。
三种玩法
1. 后台定时器
class MyPlugin extends Plugin {
async onStart() {
this.timer = setInterval(() => {
// 每分钟做一次
}, 60000);
}
async onStop() {
clearInterval(this.timer);
}
}
也可以让 AI 主动说一句话:
async onStart() {
this.timer = setInterval(async () => {
await this.context.sendMessage('提示词,让 AI 说点什么');
}, 60000);
}
2. 拦截消息
消息在发给 AI 之前经过 onUserInput,AI 回复之后经过 onLLMResponse:
class MyPlugin extends Plugin {
async onUserInput(event) {
// 给这次请求偷偷加点背景信息(用户看不到)
event.addContext('(现在是下午3点,用户在工作)');
// 修改用户说的话
// event.setText('改写后的消息');
// 阻止消息发给 AI,插件自己处理
// event.preventDefault();
}
async onLLMResponse(response) {
// AI 刚说完话,response.text 是回复内容
}
async onTTSEnd() {
// AI 说完话了(语音播放结束)
}
}
event.source 可以区分来源:'voice'(语音)、'text'(文字)、'barrage'(弹幕)
3. 给 AI 注册工具
AI 可以在对话中主动调用这些工具:
class MyPlugin extends Plugin {
getTools() {
return [{
type: 'function',
function: {
name: 'get_weather',
description: '查询城市天气',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '城市名' }
},
required: ['city']
}
}
}];
}
async executeTool(name, params) {
if (name === 'get_weather') {
return `${params.city}:晴,25°C`;
}
}
}
context 能做什么
this.context 是插件和应用之间的桥梁:
// 打日志(显示在终端)
this.context.log('info', '消息');
this.context.log('warn', '警告');
// 读配置
this.context.getConfig() // 整个 config.json
this.context.getPluginConfig() // 本插件的配置(已解析,直接用)
// 临时存数据(重启清空)
this.context.storage.set('key', value);
this.context.storage.get('key');
// 让 AI 主动说一句话(走完整 LLM + TTS 流程)
await this.context.sendMessage('提示词');
// 往系统提示词里注入内容(每次 AI 请求都会带着,直到 remove)
this.context.addSystemPromptPatch('patch-id', '你记住这件事');
this.context.removeSystemPromptPatch('patch-id');
// 获取当前对话历史
this.context.getMessages();
// 插件自己偷偷问 AI(不进入对话历史)
const result = await this.context.callLLM('帮我总结一下');
// UI 操作
this.context.showSubtitle('在屏幕上显示字幕', 3000); // 持续3秒
this.context.triggerEmotion('happy'); // 触发 Live2D 表情
// 获取另一个插件的实例(插件间通信)
const other = this.context.getPlugin('other-plugin-name');
完整钩子列表
| 钩子 | 什么时候触发 | 常见用途 |
|---|---|---|
onInit() |
插件加载时 | 读配置、初始化变量 |
onStart() |
应用就绪后 | 启动定时器、连接服务 |
onStop() |
应用关闭前 | 清理定时器、保存数据 |
onUserInput(event) |
用户消息发给 AI 之前 | 注入上下文、过滤词、修改消息 |
onLLMRequest(request) |
即将调用 LLM 时 | 修改 messages 数组 |
onLLMResponse(response) |
AI 回复之后、TTS 之前 | 记录回复、触发副作用 |
onTTSText(text) → string |
TTS 处理文本时 | 翻译、替换词(只影响语音,字幕不变) |
onTTSStart(text) |
语音开始播放 | 同步动画、状态标记 |
onTTSEnd() |
语音播放结束 | 重置定时器、下一步操作 |
所有方法都是可选的,不需要的不用写。
Python 插件
不想写 JS 可以用 Python,需要系统已安装 Python 3。
目录结构和 JS 一样,区别是 metadata.json 里多一行 "lang": "python":
{
"name": "my-py-plugin",
"displayName": "我的 Python 插件",
"version": "1.0.0",
"author": "你",
"lang": "python",
"main": "index.py"
}
index.py
from plugin_sdk import Plugin, run
class MyPlugin(Plugin):
async def on_start(self):
self.context.log('info', '启动了')
async def on_user_input(self, event):
event.add_context('(注入的信息)')
async def on_tts_end(self):
pass
if __name__ == '__main__':
run(MyPlugin)
钩子名称和 JS 一样,只是改成了 Python 的下划线风格(onStart → on_start)。
Python context 可用方法:
self.context.log('info', '消息')
self.context.send_message('让 AI 主动说这句话') # 走完整 LLM + TTS
self.context.get_config() # 整个 config.json
self.context.get_plugin_config() # 本插件的配置(已解析,直接用)
self.context.storage.get('key')
self.context.storage.set('key', value)
Python 的 on_user_input:
event.text # 用户输入
event.source # 'voice' | 'text' | 'barrage'
event.add_context(text) # 注入背景信息(用户看不到)
event.set_text(text) # 修改消息内容
event.prevent_default() # 阻止发给 AI
event.stop_propagation() # 阻止后续插件处理
Python 插件用 asyncio,定时器这样写:
import asyncio
class MyPlugin(Plugin):
async def on_start(self):
self._task = asyncio.create_task(self._loop())
async def on_stop(self):
self._task.cancel()
async def _loop(self):
try:
while True:
await asyncio.sleep(60)
self.context.send_message('让 AI 说点什么')
except asyncio.CancelledError:
pass
发布到插件广场
请查看:插件发布 教程