Skip to content
This repository was archived by the owner on Jul 1, 2026. It is now read-only.

Latest commit

 

History

History
584 lines (459 loc) · 14.4 KB

File metadata and controls

584 lines (459 loc) · 14.4 KB

插件开发指南

SillyGirl 的插件系统基于 Goja JavaScript 引擎,完整支持 ECMAScript 5.1。插件以 .js 文件形式存在,通过顶部注释声明元数据,由框架自动加载、匹配和执行。

目录

插件结构

一个最小插件由注释元数据和执行代码组成:

/**
 * @title HelloWorld
 * @rule raw ^你好$
 */

s.reply("Hello World!");

插件文件可以包含多个 @rule,每条规则独立匹配:

/**
 * @title 多功能助手
 * @rule raw ^你好$
 * @rule raw ^再见$
 * @rule 天气 [城市]
 */

const content = s.getContent();
if (content === "你好") {
  s.reply("Hello!");
} else if (content === "再见") {
  s.reply("Goodbye!");
} else {
  const city = s.param("城市");
  s.reply(`${city}今天天气晴朗!`);
}

注释元数据

字段 类型 必填 说明
title string 插件标题,显示在管理面板和插件市场
rule string 消息匹配规则,支持多行。详见规则匹配语法
cron string 定时任务表达式,支持多行,格式为 平台 cron表达式
priority number 匹配优先级,数字越大越优先,默认 0
on_start boolean true 时随系统启动执行一次,常用于初始化服务
module boolean true 时表示为模块插件,不响应消息规则
web boolean true 时表示为 Web 插件,可声明 HTTP 路由
version string 版本号,如 v1.0.0
author string 作者名
description string 插件描述
icon string 插件图标 URL
public boolean true 时允许发布到插件市场
disable boolean true 时禁用插件
admin boolean true 时仅管理员可触发
platform string 限制平台,如 qqweb

元数据示例

/**
 * @title 每日早报
 * @rule raw ^早报$
 * @cron qq 0 9 * * *
 * @cron web 0 9 * * *
 * @priority 10
 * @version v1.2.0
 * @author cdle
 * @description 每天早上9点推送新闻早报
 * @icon https://example.com/icon.png
 * @public true
 */

全局对象与 API

sender (s)

当前消息的 Sender 对象,是插件中最核心的交互入口。

用户信息

s.getUserId()       // 获取用户ID(string)
s.getUserName()     // 获取用户昵称(string)
s.getChatId()       // 获取群聊ID,私聊时为空字符串(string)
s.getChatName()     // 获取群聊名称(string)
s.getMessageId()    // 获取消息ID(string)
s.getPlatform()     // 获取平台类型,如 "qq"、"web"(string)
s.getBotId()        // 获取当前机器人ID(string)
s.isAdmin()         // 判断用户是否为管理员(boolean)

内容操作

s.getContent()      // 获取消息原始内容(string)
s.setContent(text)  // 修改当前消息内容(影响后续插件匹配)
s.continue()        // 继续匹配后续规则(默认匹配成功即停止)

回复消息

s.reply("文本")     // 回复文本消息,返回 { message_id, error }
s.reply("文本1", "文本2")  // 多段回复

参数捕获

// 对于规则 "天气 [城市]"
s.param("城市")     // 通过名称获取捕获组
s.param(1)          // 通过索引获取第1个捕获组(从1开始)
s.get(1)            // param 的别名
s.getAllMatch()     // 获取所有匹配组(二维数组)

群管功能

s.kick(userId)          // 踢出群成员
s.unkick(userId)        // 取消踢出
s.ban(userId, duration) // 禁言,duration 为秒数
s.unban(userId)         // 解除禁言
s.recallMessage(messageId)  // 撤回消息

Bucket(name)

持久化键值存储,数据自动持久化到 BoltDB 或 Redis。

const bucket = Bucket("myapp");

// 基础读写
bucket.set("key", "value");
const value = bucket.get("key", "default_value");  // 支持默认值

// 类型自动转换
bucket.set("count", 100);        // 存储为数字
bucket.set("enabled", true);     // 存储为布尔值
bucket.set("data", { a: 1 });    // 存储为对象(自动 JSON 序列化)

// 监听变更
bucket.watch("key", (oldValue, newValue, key) => {
  console.log(`${key} changed: ${oldValue} -> ${newValue}`);
});

// 其他方法
bucket.keys();       // 获取所有键名(string[])
bucket.getAll();     // 获取所有键值(object)
bucket.delete("key"); // 删除键
bucket.empty();      // 清空 bucket
bucket.len();        // 获取键数量(number)

作用域说明:每个 Bucket 是独立的命名空间,不同插件建议使用不同的 Bucket 名称,避免键冲突。

Cron()

定时任务调度器,支持标准 Cron 表达式。

const task = Cron();

// 添加任务(6字段:秒 分 时 日 月 周)
const { id, error } = task.add("*/5 * * * * *", () => {
  console.log("每5秒执行一次");
});

// 添加任务(5字段:分 时 日 月 周,秒自动补0)
const { id, error } = task.add("0 9 * * *", () => {
  console.log("每天早上9点执行");
});

// 移除任务
task.remove(id);

注意:插件中更推荐通过 @cron 注释声明定时任务,由框架统一管理生命周期。

Express()

HTTP 服务路由注册(基于内置 Web 服务器)。

const app = Express();

app.get("/hello", (req, res) => {
  res.send("Hello World!");
});

app.post("/api/data", (req, res) => {
  const body = req.body();
  res.json({ success: true, data: body });
});

注意:Web 插件需要声明 @web true 元数据,且框架会自动清理已注册的路由。

其他全局函数

sleep(ms)           // 同步阻塞睡眠(毫秒)
md5(str)            // MD5 哈希
uuid()              // 生成 UUID
running()           // 判断当前插件是否仍在运行(boolean)
fmt.Sprintf(format, ...args)  // 格式化字符串
fmt.Printf(format, ...args)   // 格式化输出
time.Now()          // 获取当前时间对象
time.Sleep(ms)      // 睡眠(毫秒)
time.Unix(sec)      // Unix 时间戳转时间对象
time.Parse(str, layout, locale)  // 解析时间字符串

规则匹配语法

规则(@rule)支持多种语法,框架会自动转换为正则表达式进行匹配。

基础规则

/**
 * @rule raw ^你好$      // 原始正则,完全匹配"你好"
 * @rule raw ^/help$     // 原始正则,匹配 "/help"
 * @rule 你好            // 自动转换为 ^你好$,完全匹配
 * @rule ^天气           // 以"天气"开头
 * @rule 帮助$           // 以"帮助"结尾
 */

参数捕获

/**
 * @rule 天气 [城市]              // 匹配"天气 北京",捕获"北京"
 * @rule [操作:登录,注册,退出]     // 匹配"登录"、"注册"或"退出"
 * @rule 查询 ?                   // ? 匹配任意非空白字符
 * @rule 搜索 *                   // * 匹配任意内容(包括空白)
 */

可选参数

/**
 * @rule 天气 [城市?]             // ? 表示可选,匹配"天气"或"天气 北京"
 */

优先级与冲突

当多条规则可能同时匹配一条消息时,框架按以下顺序决定执行:

  1. 监听规则s.listen 注册的)优先于普通规则
  2. 高 priority 优先于低 priority
  3. 同一优先级下,先加载的插件优先

使用 s.continue() 可以让当前插件执行完毕后继续匹配后续规则:

/**
 * @title 日志中间件
 * @rule *
 * @priority 999
 */

console.log("收到消息:", s.getContent());
s.continue();  // 继续让其他插件处理

消息监听与会话

s.listen() 是实现对话式交互的核心 API,支持等待用户后续输入并按规则匹配。

基础用法

/**
 * @title 注册流程
 * @rule raw ^注册$
 */

s.reply("请输入你的用户名:");
const result = s.listen({
  rules: ["raw ^(.+)$"],  // 捕获任意输入
  timeout: 30000,          // 30秒超时
  handle: (s2) => {
    const username = s2.param(1);
    s2.reply(`用户名 "${username}" 注册成功!`);
  },
});

if (!result) {
  s.reply("注册超时,请重试。");
}

监听选项

s.listen({
  rules: ["规则1", "规则2"],        // 匹配规则数组
  timeout: 10000,                   // 超时时间(毫秒)
  handle: (s2) => { ... },          // 匹配后的回调函数
  private: true,                    // 允许在私聊中触发
  group: true,                      // 允许在群聊中触发
  require_admin: false,             // 是否要求管理员权限
  allow_platforms: ["qq"],          // 限制平台
  prohibit_platforms: ["web"],      // 禁止平台
  allow_users: ["12345"],           // 仅允许指定用户
  allow_groups: ["67890"],          // 仅允许指定群组
  user_id: s.getUserId(),           // 仅监听当前用户
  chat_id: s.getChatId(),           // 仅监听当前群组
});

持久化监听

添加 "persistent" 参数可创建长期有效的监听器:

/**
 * @title 关键词监控
 * @rule raw ^启动监控$
 */

s.listen({
  rules: ["raw ^(.+)$"],
  timeout: 0,           // 0 表示永不超时
  handle: (s2) => {
    console.log("监控到消息:", s2.getContent());
    // 返回空字符串或 undefined 会继续监听
  },
}, "persistent");

s.reply("监控已启动");

HoldOn 与 GoAgain

/**
 * @title 循环输入
 * @rule raw ^开始$
 */

function ask() {
  s.reply("请输入内容(输入'结束'停止):");
  const r = s.listen({
    rules: ["raw ^(.+)$"],
    timeout: 30000,
    handle: (s2) => {
      if (s2.param(1) === "结束") {
        s2.reply("已结束");
        return;
      }
      s2.reply("你输入了:" + s2.param(1));
      return s2.holdOn("开始");  // 重新触发当前插件
    },
  });
}
ask();

HTTP 路由

声明 @web true 的插件可以注册 HTTP 路由:

/**
 * @title WebAPI 示例
 * @web true
 */

Express().get("/api/status", (req, res) => {
  res.json({
    status: "ok",
    time: new Date().toISOString(),
    plugin: "WebAPI 示例",
  });
});

Express().post("/api/echo", (req, res) => {
  const body = req.body();
  res.json({ echo: body });
});

Request / Response 对象

req.url()           // 请求 URL
req.method()        // 请求方法
req.header("key")   // 获取请求头
req.body()          // 获取请求体(已解析 JSON)
req.query("key")    // 获取 URL 参数
req.path()          // 请求路径
req.param("key")    // 获取路径参数

res.send("text")    // 发送文本响应
res.json(obj)       // 发送 JSON 响应
res.redirect(url)   // 重定向
res.status(code)    // 设置状态码

定时任务

通过 @cron 注释声明定时任务:

/**
 * @title 每日提醒
 * @cron qq 0 9 * * *       // 每天早上9点(QQ平台)
 * @cron web 0 12 * * *     // 每天中午12点(Web平台)
 */

s.reply("该起床啦!今天是 " + time.Now().Format("2006-01-02"));

Cron 表达式格式(5字段或6字段):

秒(可选) 分 时 日 月 周
字段 范围 特殊字符
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * /
1-12 , - * /
0-6(0=周日) , - * /

示例:

表达式 说明
*/5 * * * * 每5分钟
0 */1 * * * 每小时
0 9 * * 1-5 工作日早上9点
0 0 1 * * 每月1号零点
*/30 * * * * * 每30秒

完整示例

示例 1:记忆名字

/**
 * @title 记忆名字
 * @rule raw ^我是谁$
 * @rule 我是[姓名]
 * @version v1.0.0
 * @author cdle
 */

const user = Bucket("user_names");
const name = s.param("姓名");

if (!name) {
  const stored = user.get(s.getUserId());
  if (stored) {
    s.reply(`你是 ${stored}`);
  } else {
    s.reply("我还不知道你是谁,告诉我吧:我是[你的名字]");
  }
} else {
  user.set(s.getUserId(), name);
  s.reply(`好的,我记住你了,${name}!`);
}

示例 2:倒计时提醒

/**
 * @title 倒计时
 * @rule 倒计时 [分钟:1,2,3,5,10] 分钟
 * @version v1.0.0
 */

const minutes = parseInt(s.param(1));
s.reply(`好的,${minutes}分钟后提醒你。`);

// 使用 Cron 不太适合一次性延时,这里用 sleep
// 注意:sleep 会阻塞,长时间任务建议用 cron 或其他方式
go(() => {
  sleep(minutes * 60 * 1000);
  s.reply("⏰ 时间到了!");
});

注:实际开发中长时间后台任务建议使用 @cron 或外部调度。

示例 3:简易 HTTP API

/**
 * @title 天气查询 API
 * @web true
 * @version v1.0.0
 */

Express().get("/api/weather", (req, res) => {
  const city = req.query("city") || "北京";
  // 实际场景中这里应调用第三方天气 API
  res.json({
    city: city,
    weather: "晴",
    temperature: "25°C",
    updated_at: time.Now().Format("2006-01-02 15:04:05"),
  });
});

调试技巧

1. 使用终端模式

./sillyGirl -t

终端模式是开发插件的最快方式,修改插件后立即生效,无需重启。

2. 开启调试模式

在 Admin 面板将 sillyGirl.debug 设为 true,或在插件中:

console.log("调试信息:", someVariable);
console.debug("详细调试:", detailedInfo);
console.error("错误:", err);

3. 查看插件状态

在 Admin 面板的"插件"页面,可以查看:

  • 插件加载状态
  • 规则列表
  • 错误日志
  • 性能统计

4. 安全执行

插件运行异常时框架会自动捕获 panic,不会影响其他插件或核心服务。你可以在 Admin 面板查看具体的错误堆栈。

5. 模块复用

将公共逻辑抽取为 @module true 插件,其他插件通过 require 或全局变量复用:

/**
 * @title 工具模块
 * @module true
 */

// 定义全局工具函数
RegistFuncs["utils"] = {
  formatTime: (t) => t.Format("2006-01-02"),
  isWeekend: (t) => t.Weekday() === 0 || t.Weekday() === 6,
};