GoroBot
Go 语言编写的跨平台聊天机器人框架,用于 AI Agent 应用开发。
快速入门
支持平台
- QQ (pkg/lagrange)
- OneBot
- QQ Official (pkg/qbot)
- Telegram
特性
- 高性能(我瞎说的反正 Go 怎么也比 Node 快)
- 高扩展性(插件化设计)
- 高自订性(插件想干啥都行.jpg)
能做什么
- 方便地编写自己的插件
- 所见即所得的命令格式
- 以通用的方法回复消息
注意事项
如果有能力,请审查所有使用的代码!!! 由于语言的特性,开发者拥有极高的权限,请在使用不可信来源的插件时多加注意!
TODO
欢迎向本项目提交 Issue
Official Plugins
-
ping: bot 还在线吗?ping 一下看看 -
message_logger: 在控制台输出消息日志 -
go_plugin: 支持 go 风格热插拔式插件
Bugs
- Onebot 适配器未稳定(http连接未完善)
快速入门
安装框架
本框架仍然处于开发状态,代码迭代速度快,建议使用第二种安装方法。
从代码包导入
go get github.com/Jel1ySpot/GoroBot- 在
main.go中导入包
从仓库导入
Fork 并克隆仓库,在 main.go 文件与 plugins/ 文件夹中编写业务逻辑代码(已添加至 .gitignore)。
这样既能够适应框架的快速迭代,还可以更方便向本仓库提交 PR
(对,我 们 鼓励任何人提交修复或功能代码,也欢迎在 github issue 中提出在使用过程中的任何问题与建议)
示范:
#/bin/bash
git clone https://github.com/PathtoYou/rRepo
cd rRpot
mkdir plugins
touch main.go
最小示例
这是一个最小示例:
package main
import (
GoroBot "github.com/Jel1ySpot/GoroBot/pkg/core"
)
func main() {
grb := GoroBot.Create()
if err := grb.Run(); err != nil {
panic(err)
}
}
第一次运行上面的代码会报错,提示我们要填写配置文件(conf/config.json)。我们可以按照以下格式填写:
{
"log_level": 1, // 日志等级。 0:Debug, 1:Info, 2:Warning, 3:Error
"owner": { // 机器人所有者
"qq": "你的QQ号" // 格式:"平台": "ID"
}
}
填写完成后再次启动,可以发现报错消失了,但控制台没有输出。这是因为这段代码没有载入任何逻辑代码。在这段代码中,我们创建了一个 GoroBot 实例(为了方便,在文档中会用 grb 代表 GoroBot 实例),并通过 grb.Run() 运行了它。
引入适配器
GoroBot 框架使用适配器支持各种 IM 平台。适配器也是一种插件。让我们尝试引入一个适配器:
import LgrClient "github.com/Jel1ySpot/GoroBot/pkg/lagrange" // 引入模块
// 在 main 函数中:
grb.Use(LgrClient.Create())
插件使用 Create() 创建一个服务,我们可以使用 grb.Use(*Service) 来使用一个服务。运行这段代码,如无意外还需要填写配置文件,详细请见 pkg/lagrange。
配置好服务后再次运行,没有意外的话机器人服务就启动成功了。
目前支持的适配器:
| 适配器 | 导入路径 | 说明 |
|---|---|---|
| Lagrange | pkg/lagrange | QQ 平台 |
| OneBot | pkg/onebot | OneBot 协议(WebSocket) |
| QBot | pkg/qbot | QQ 官方机器人 |
| Telegram | pkg/telegram | Telegram Bot API |
每个适配器首次运行后会在 conf/<adapter>/ 下生成配置文件,填写后重新启动即可。
使用插件
同样是一个例子:
import "github.com/Jel1ySpot/GoroBot/example_plugin/message_logger" // 引入模块
// 在 main 函数中
grb.Use(message_logger.Create())
再次运行代码,尝试向机器人账号发送消息,如果控制台中出现了刚才发送的消息,代表框架已经搭建成功了!开始编写属于你自己的 IM Chatbot 吧!
接下来应该做什么
配置文件
GoroBot 采用模块化的配置管理方案。核心框架与各个适配器、插件独立管理各自的配置文件。配置管理基于 conic 库,支持 JSON / YAML 等格式、结构体动态绑定与配置热重载(WatchConfig)。
首次运行机器人时,各模块会在 conf/ 目录下自动生成默认配置文件。
目录结构
典型的 conf/ 配置目录结构如下:
conf/
├── config.json # 框架核心主配置文件
├── qbot/ # QQ 官方机器人适配器配置
│ └── config.yaml
├── onebot/ # OneBot 适配器配置
│ └── config.json
├── lagrange/ # Lagrange 适配器配置
│ └── config.json
└── telegram/ # Telegram 适配器配置
└── config.json
框架核心配置文件 (conf/config.json)
主配置文件控制框架的全局行为,如日志输出级别、机器人所有者(管理员)映射以及资源文件路径等。
示例配置
{
"log_level": 1,
"owner": {
"qq": "100000000",
"qbot:<your_bot_appid>": "qbot:user&<your_user_openid>",
"telegram:<your_bot_id>": "telegram:user&<your_user_id>"
},
"admin": {
"qq": ["100000001", "100000002"],
"telegram:<your_bot_id>": ["telegram:user&<admin_user_id>"]
},
"resource_path": "./data/resources"
}
字段说明
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
log_level | int | 1 | 全局日志输出级别,数值越大输出越详细(见下表) |
owner | object | {} | 机器人最高所有者(等级 5)映射,键为机器人上下文 ID 或平台标识,值为 Universal ID 或用户 ID |
admin | object | {} | 机器人管理员(等级 4)映射,键为上下文 ID 或平台标识,值为用户 ID(支持单字符串或字符串列表) |
resource_path | string | "" | 资源文件保存根路径(可选) |
日志级别 (log_level) 说明
| 数值 | 等级名称 | 说明 |
|---|---|---|
-3 | Fatal | 仅输出致命错误 |
-2 | Error | 输出错误信息及以上 |
-1 | Announcement / Success | 输出成功与公告信息及以上 |
0 | Warning | 输出警告信息及以上 |
1 | Info | 默认级别,输出常规运行信息及以上 |
2 | Debug | 输出详细调试日志及以上(包含接收/发送数据调试) |
适配器配置文件
1. QQ 官方机器人 (conf/qbot/config.yaml)
QBot 适配器用于接入 QQ 开放平台官方机器人。默认使用 WebSocket Gateway 长连接,也支持 Webhook 回调或双向并存模式。
示例配置
debug: false
mode: "websocket" # 连接模式: "websocket" (默认), "webhook", 或 "both"
intents: 0 # 订阅事件掩码,留空或 0 则使用框架内置默认值
api:
appid: "your_app_id"
secret: "your_secret_key"
# 可选:仅在 mode 为 "webhook" 或 "both",或者需要对外提供图片等静态资源服务时配置
webhook:
host: "0.0.0.0"
port: 8443
path: "/bot"
base_url: "https://your.domain.net:8443"
tls:
cert_path: "/path/to/server.crt"
key_path: "/path/to/server.key"
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
debug | bool | 是否启用 QBot 详细调试日志 |
mode | string | 连接模式:"websocket"(推荐)、"webhook" 或 "both" |
intents | int | 事件订阅 Intents 掩码,传 0 默认订阅群聊、频道和私聊常用事件 |
api.appid | string | QQ 开放平台的机器人 AppID |
api.secret | string | QQ 开放平台的机器人 AppSecret。注:若留空且在终端运行,QBot 会在终端展示登录二维码,手机 QQ 扫码后自动获取凭证并加密写回配置文件 |
webhook.host | string | Webhook 监听地址(默认 0.0.0.0) |
webhook.port | uint | Webhook 监听端口(如 8443) |
webhook.path | string | Webhook 回调 URL 路径(如 /bot) |
webhook.base_url | string | 机器人的公网访问基础地址,用于回调校验及静态媒体回源 |
webhook.tls.cert_path | string | TLS/SSL 证书文件路径(可选,若配置则开启 HTTPS) |
webhook.tls.key_path | string | TLS/SSL 私钥文件路径 |
2. OneBot 适配器 (conf/onebot/config.json)
OneBot 适配器支持 OneBot v11 协议标准。支持 HTTP、WebSocket 客户端(正向 WS)以及反向 WebSocket(ws_reverse)三种通信模式。
示例配置(正向 WebSocket 模式)
{
"mode": "ws",
"ws": {
"host": "127.0.0.1",
"port": 3001,
"access_token": "your_token"
},
"message_format": "array",
"heartbeat": {
"enable": true,
"interval": 30000
},
"ignore_self": true,
"command_prefix": "/",
"debug": false
}
示例配置(反向 WebSocket 模式)
{
"mode": "ws_reverse",
"ws_reverse": {
"host": "0.0.0.0",
"port": 8080,
"path": "/onebot/v11/ws",
"access_token": "",
"reconnect_interval": 300
},
"message_format": "array",
"ignore_self": true,
"command_prefix": "/"
}
示例配置(HTTP 模式)
{
"mode": "http",
"http": {
"host": "127.0.0.1",
"port": 5700,
"post_url": "http://127.0.0.1:5701",
"access_token": "",
"secret": "",
"timeout": 30
},
"message_format": "array",
"ignore_self": true,
"command_prefix": "/"
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
mode | string | 必填。连接模式:"ws"、"ws_reverse" 或 "http" |
ws | object | 正向 WebSocket 客户端配置(当 mode 为 "ws" 时必填) |
ws.host | string | OneBot 服务端的主机地址 |
ws.port | int | OneBot 服务端的 WebSocket 端口 |
ws.access_token | string | 鉴权令牌(可选) |
ws_reverse | object | 反向 WebSocket 服务端配置(当 mode 为 "ws_reverse" 时必填) |
ws_reverse.host | string | 本地监听地址 |
ws_reverse.port | int | 本地监听端口 |
ws_reverse.path | string | WebSocket 路径(如 "/onebot/v11/ws") |
ws_reverse.access_token | string | 客户端连接时的鉴权 Token |
ws_reverse.reconnect_interval | int | 重连超时时间(毫秒,默认 300) |
http | object | HTTP 交互模式配置(当 mode 为 "http" 时必填) |
http.host | string | HTTP 服务端地址 |
http.port | int | HTTP 服务端端口 |
http.post_url | string | 事件上报地址 |
http.access_token | string | 鉴权 Token |
http.secret | string | HTTP 上报事件签名密钥 |
http.timeout | int | HTTP 请求超时时间(秒,默认 30) |
message_format | string | 消息格式:"array"(推荐,CQ 码数组)或 "string"(纯字符串) |
heartbeat.enable | bool | 是否启用心跳保活机制(默认 true) |
heartbeat.interval | int | 心跳上报间隔(毫秒,默认 30000) |
rate_limit.enable | bool | 是否开启 API 频率限制(默认 false) |
rate_limit.interval | int | 限速间隔(毫秒) |
ignore_self | bool | 是否忽略自身发送的消息(默认 true) |
command_prefix | string | 命令前缀(默认 "/") |
debug | bool | 是否输出 OneBot 调试日志 |
3. Lagrange 适配器 (conf/lagrange/config.json)
Lagrange 适配器基于 LagrangeGo 实现 QQ 客户端协议支持。
示例配置
{
"app_info": "linux 9.0.90",
"sign_server_url": "https://sign.example.com",
"music_sign_server_url": "",
"command_prefix": "/",
"account": {
"uin": 100000000,
"password": "",
"sig_path": "keystore.json"
},
"ignore_self": true
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
app_info | string | 模拟客户端协议版本信息(如 "linux 9.0.90") |
sign_server_url | string | Lagrange 签名服务器地址 |
music_sign_server_url | string | 音乐卡片签名服务地址(可选) |
command_prefix | string | 命令前缀(默认 "/") |
account.uin | uint32 | QQ 账号 |
account.password | string | QQ 密码(支持留空并通过扫码验证登录) |
account.sig_path | string | 登录凭证保存相对路径(保存在 conf/lagrange/ 目录下) |
ignore_self | bool | 是否忽略自身消息 |
4. Telegram 适配器 (conf/telegram/config.json)
Telegram 适配器用于接入 Telegram Bot API。
示例配置
{
"token": "your_bot_token_from_botfather",
"server_url": ""
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
token | string | 必填。Telegram Bot Token,可向 @BotFather 创建获取 |
server_url | string | 自定义 Telegram API 地址(留空则使用官方默认 https://api.telegram.org,适合配置本地代理或反代加速) |
Universal ID (统一实体标识) 说明
为了统一跨平台的用户、群组与消息寻址,GoroBot 采用了 Universal ID 机制。
格式定义为:
protocol:type&arg1&arg2...
常见格式示例
| 平台 | 实体类型 | Universal ID 示例 |
|---|---|---|
| QBot | 用户 | qbot:user&<openid> |
| QBot | 群聊 | qbot:group&<group_openid> |
| QBot | 频道 | qbot:guild&<guild_id> |
| Lagrange / OneBot | 用户 | lagrange:user&<uin> / onebot:user&<uin> |
| Lagrange / OneBot | 群聊 | lagrange:group&<group_id> / onebot:group&<group_id> |
| Telegram | 用户 / 群聊 | telegram:user&<chat_id> / telegram:group&<chat_id> |
在 conf/config.json 的 owner 配置中,可通过配置该 ID 赋予特定用户管理员权限。
在自定义插件中管理配置
开发者编写插件时,也可以方便地利用 conic 库为自己的插件提供独立的配置文件:
package myplugin
import (
"path"
"github.com/Jel1ySpot/conic"
GoroBot "github.com/Jel1ySpot/GoroBot/pkg/core"
"github.com/Jel1ySpot/GoroBot/pkg/util"
)
type Config struct {
Greeting string `json:"greeting"`
Limit int `json:"limit"`
}
type Service struct {
conic *conic.Conic
config Config
}
func (s *Service) Init(grb *GoroBot.Instant) error {
configDir := "conf/myplugin/"
configFile := path.Join(configDir, "config.json")
s.conic = conic.New()
s.conic.SetConfigFile(configFile)
s.conic.WatchConfig()
s.conic.BindRef("", &s.config)
if !util.FileExists(configFile) {
_ = util.MkdirIfNotExists(configDir)
s.config = Config{Greeting: "你好", Limit: 10}
_ = s.conic.WriteConfig() // 自动写出默认配置
}
return s.conic.ReadConfig()
}
事件系统
GoroBot 通过事件系统实现了消息监听等功能。这一设计允许插件开发者和用户编写简洁、通用的代码来扩展和实现各种功能。
以消息事件举例
以下示例代码展示了如何使用事件系统注册和注销一个监听器:
import botc "github.com/Jel1ySpot/GoroBot/pkg/core/bot_context"
var del func()
del, _ = grb.On(GoroBot.MessageEvent(func(ctx botc.MessageContext) {
if ctx.String() == "ping" {
_, _ = ctx.ReplyText("🏓")
del()
}
}))
在这段代码中,通过调用 grb.On 方法,注册了一个监听消息事件的监听器。监听器的回调函数接收一个 ctx(消息内容上下文)参数。当接收到的消息内容为 "ping" 时,调用 ctx.ReplyText 方法回复 "🏓",并通过 del 函数注销该监听器,从而实现了单次响应特定消息的功能。
MessageEvent() 函数接受一个回调函数作为参数,并返回一个 Name 属性为 "message" 的 EventHandler 实例。框架通过将用户提供的回调函数转换为 func(args ...any) error 的通用签名形式,简化了事件注册的流程。这种设计使开发者无需关心底层实现的细节,便可直观地使用框架或插件提供的事件注册器。
常用的消息上下文方法
ctx.String()— 消息文本预览ctx.Message()— 获取完整消息结构(*botc.BaseMessage)ctx.Protocol()— 消息来源平台("lagrange"、"telegram"等)ctx.SenderID()— 发送者 IDctx.ReplyText(...)— 回复纯文本ctx.Reply(elements)— 回复消息元素ctx.BotContext()— 获取所在平台的适配器上下文
如果你需要的是对特定格式的消息做出回复(比如
/command arg1 arg2),那你应该看看命令系统。
中间件
中间件使用 grb.Middleware(MiddlewareCallback) 注册。跟事件一样,这个方法返回一个函数用于注销该中间件。
MiddlewareCallback 的签名为:
func(msg botc.MessageContext, next func(...MiddlewareCallback) error) error
跟消息事件不同的是,中间件的回调函数参数中有一个额外的参数 next,只有调用了它才会进入接下来的流程。
使用中间件实现的 ping
grb.Middleware(func(msg botc.MessageContext, next func(...GoroBot.MiddlewareCallback) error) error {
if msg.String() == "ping" {
_, _ = msg.ReplyText("🏓")
}
return next()
})
prepare 模式
传入第二个参数 true 可以注册一个 prepare 中间件,它会在所有普通中间件之前执行:
grb.Middleware(func(msg botc.MessageContext, next func(...GoroBot.MiddlewareCallback) error) error {
// 这段逻辑会在所有普通中间件之前执行
return next()
}, true)
命令系统
命令系统是 GoroBot 中处理用户指令的核心。它提供了一个链式 API 来注册命令,支持参数、选项、子命令和别名。
注册一个命令
最简单的命令长这样:
import "github.com/Jel1ySpot/GoroBot/pkg/core/command"
delFn, _ := grb.Command("hello").
Action(func(ctx *command.Context) error {
_, _ = ctx.ReplyText("你好!")
return nil
}).
Build()
用户发送 /hello 时,机器人会回复 "你好!"。Build() 返回一个注销函数,调用它可以移除这个命令。
命令描述
给命令加上描述,Telegram 适配器会自动同步到客户端的命令菜单:
grb.Command("hello").
Description("打个招呼").
Action(handler).
Build()
参数
参数是命令后面跟着的值,比如 /echo 你好 中的 "你好":
grb.Command("echo").
Argument("content", command.String, true, "要回复的内容").
Action(func(ctx *command.Context) error {
_, _ = ctx.ReplyText(ctx.KvArgs["content"])
return nil
}).
Build()
- 第一个参数是名称,在
ctx.KvArgs中用它来取值 - 第二个参数是类型:
command.String、command.Number、command.Boolean - 第三个参数表示是否必填
- 第四个参数是帮助文本
取类型化的值可以用 command.GetInt()、command.GetFloat()。
选项
选项就是 --flag 或 -f 这种写法:
grb.Command("search").
Option("count", "n", command.Number, false, "10", "结果数量").
Argument("keyword", command.String, true, "关键词").
Action(func(ctx *command.Context) error {
count := command.GetInt(ctx.KvArgs["count"])
keyword := ctx.KvArgs["keyword"]
// ...
return nil
}).
Build()
用户可以这样用:/search --count 5 golang 或 /search -n 5 golang。
子命令
大的命令可以拆分成子命令:
cmd := grb.Command("plugin")
cmd.SubCommand("list").
Action(func(ctx *command.Context) error {
// /plugin list
return nil
}).Build()
cmd.SubCommand("load").
Argument("name", command.String, true, "插件名").
Action(func(ctx *command.Context) error {
// /plugin load myplugin
return nil
}).Build()
cmd.Build()
注意最后要调用根命令的 Build() 来完成注册。
别名
别名让命令可以通过正则表达式匹配触发。比如骰子插件用 d6、d20 这样的写法:
grb.Command("dice").
Argument("upper_bound", command.Number, false, "骰子点数上限").
Alias(`^d(\d+)$`, func(ctx *command.Context) *command.Context {
_ = ctx.AppendArg(ctx.String()[1:])
return ctx
}).
Action(func(ctx *command.Context) error {
limit := command.GetInt(ctx.KvArgs["upper_bound"])
// ...
return nil
}).
Build()
别名的第一个参数是正则表达式,第二个参数是一个转换函数,可以在里面用 ctx.AppendArg() 把匹配到的内容追加为参数。转换函数可以传 nil,这样匹配到后会直接触发命令。
命令上下文
command.Context 嵌入了 botc.MessageContext,所以消息上下文的方法都能用。额外提供了以下内容:
ctx.KvArgs— 命名参数的键值对(map[string]string)ctx.Arguments— 位置参数列表([]string)ctx.Commands— 匹配到的命令/子命令路径ctx.ReplyText(...)— 回复文本ctx.Message()— 获取原始消息
在插件中使用
在插件的 Init 中注册命令,在 Release 中清理:
func (s *Service) Init(grb *GoroBot.Instant) error {
s.bot = grb
delFn, _ := grb.Command("mycommand").
Description("我的命令").
Action(func(ctx *command.Context) error {
_, _ = ctx.ReplyText("收到!")
return nil
}).
Build()
s.releaseFunc = append(s.releaseFunc, delFn)
return nil
}
更完整的示例可以参考
example_plugin/中的代码。
插件系统
GoroBot 的一切功能都是通过插件实现的,适配器也是插件。编写一个插件其实很简单,只需要实现三个方法就行。
插件骨架
每个插件都要实现 Service 接口:
package myplugin
import GoroBot "github.com/Jel1ySpot/GoroBot/pkg/core"
type Service struct {
bot *GoroBot.Instant
releaseFunc []func()
}
func (s *Service) Name() string {
return "MyPlugin"
}
func Create() *Service {
return &Service{}
}
func (s *Service) Init(grb *GoroBot.Instant) error {
s.bot = grb
// 在这里注册命令、事件监听器、中间件等
return nil
}
func (s *Service) Release(grb *GoroBot.Instant) error {
for _, fn := range s.releaseFunc {
fn()
}
return nil
}
生命周期
Create()— 创建插件实例。这时候框架还没启动grb.Use(plugin)— 注册插件Init()— 框架启动时调用。在这里注册命令、事件、中间件Release()— 框架关闭时调用。清理资源,注销注册过的东西
注册命令
func (s *Service) Init(grb *GoroBot.Instant) error {
s.bot = grb
delFn, _ := grb.Command("ping").
Description("Ping 测试").
Action(func(ctx *command.Context) error {
_, _ = ctx.ReplyText("🏓")
return nil
}).
Build()
s.releaseFunc = append(s.releaseFunc, delFn)
return nil
}
命令系统的详细用法参见 命令系统。
监听事件
del, _ := grb.On(GoroBot.MessageEvent(func(ctx botc.MessageContext) {
log.Info("收到消息: %s", ctx.String())
}))
s.releaseFunc = append(s.releaseFunc, del)
事件系统的详细用法参见 事件系统。
使用中间件
del := grb.Middleware(func(msg botc.MessageContext, next func(...GoroBot.MiddlewareCallback) error) error {
// 在消息到达事件和命令之前做点什么
return next()
})
s.releaseFunc = append(s.releaseFunc, del)
中间件的详细用法参见 中间件系统。
构建消息
如果你需要回复图片、引用、@某人之类的复杂消息:
_, _ = ctx.BotContext().NewMessageBuilder().
Text("看看这张图").
ImageFromFile("/path/to/image.png").
ReplyTo(ctx)
MessageBuilder 支持的方法:
Text(text)— 文字ImageFromFile(path)/ImageFromUrl(url)/ImageFromData(bytes)— 图片Quote(baseMsg)— 引用消息Mention(userID)— @某人ReplyTo(msgCtx)— 作为回复发送Send(chatID)— 发送到指定聊天
使用数据库
框架提供了一个可选的 SQLite 数据库:
db := grb.Database() // *sql.DB,如果没有打开数据库就是 nil
if db != nil {
// 用标准 database/sql 操作
}
清理资源
很重要:在 Init 里注册的东西,一定要在 Release 里清理掉。所有 grb.On()、grb.Command().Build()、grb.Middleware() 都会返回一个注销函数,把它们收集起来,在 Release 里逐个调用就行了。
文件组织
把插件放在自己的包里就好。简单的插件一个 service.go 搞定,复杂一点的可以拆成多个文件:
example_plugin/myplugin/
├── service.go # 生命周期(Init / Release)
├── commands.go # 命令注册
└── ...
示例插件在
example_plugin/,是最好的参考。
Wasm 插件系统
pkg/wasm_plugin 是 GoroBot 基于 Extism(底层为纯 Go 的 wazero)构建的 WebAssembly 沙箱插件引擎。
相比于原生的 Go .so 动态库,Wasm 插件具备以下优势:
- 多语言开发:支持使用 Rust、Go (TinyGo)、C/C++、Zig、AssemblyScript、TypeScript 等几乎所有能编译为 Wasm 的语言编写插件。
- 真正的热重载与完全卸载:Wasm 模块实例可在不重启主程序的情况下随时创建与销毁,内存立即释放,彻底解决 Go 原生
plugin无法卸载的问题。 - 强安全沙箱隔离:插件运行在严格受控的沙箱环境中,默认无法访问未授权的主机文件与系统调用。
- 纯 Go 零 CGO 依赖:全平台通用(Windows、Linux、macOS 均可编译运行)。
快速启用
在你的入口文件(如 main.go)中注册 wasm_plugin 服务:
package main
import (
GoroBot "github.com/Jel1ySpot/GoroBot/pkg/core"
"github.com/Jel1ySpot/GoroBot/pkg/wasm_plugin"
)
func main() {
grb := GoroBot.Create()
// 注册 Wasm 插件管理器
grb.Use(wasm_plugin.Create())
if err := grb.Run(); err != nil {
panic(err)
}
}
插件存放与数据隔离
1. 插件搜索目录
默认在运行目录下的 plugin/ 中搜索 .wasm 文件。
plugin/dice.wasm对应的插件 ID 为dice。plugin/games/guess.wasm对应的插件 ID 为games/guess。
2. 独立数据目录(文件系统沙箱)
每个插件拥有专属的数据存储目录:data/<plugin_id>/。
- 宿主自动为每个插件将该目录挂载至容器内部的
/与/data。 - 插件在 Wasm 代码中执行标准文件操作(如 Rust 的
std::fs或 TinyGo 的os)时,所有读写均被严格限制在data/<plugin_id>/内,无法越权访问宿主系统的其他文件。
消息管理指令
管理指令默认仅机器人的 Owner 拥有执行权限(采用 Fail-Closed 默认拒绝鉴权)。
| 指令 | 说明 |
|---|---|
/wasm lookup | 扫描 plugin/ 目录,发现新增的 .wasm 插件 |
/wasm list | 列出所有已识别的插件及其运行状态(✅ 已启用 / ❎ 未启用) |
/wasm load <id\|all> | 加载或重新加载指定的插件(或全部插件),实现即时热重载 |
/wasm enable <id\|all> | 启用已发现但尚未启用的插件 |
/wasm disable <id\|all> | 禁用指定插件,注销所有命令、关闭监听端口并彻底释放内存 |
宿主 API (Host Functions)
Wasm 插件可以通过导入函数与 GoroBot 宿主进行双向交互。接口同时挂载在 extism:host/user(Extism PDK 默认命名空间)和 gorobot 命名空间下。
1. 日志输出
- 函数名:
gorobot_log(level, msg) - 说明:接入 GoroBot 统一日志系统输出。
level可为debug、info、warn、error。
2. 注册消息指令
- 函数名:
gorobot_register_command(def_json) - 说明:向 GoroBot 注册机器人的消息指令。
- 入参格式:
{ "name": "dice", "description": "掷骰子插件", "arguments": [ {"name": "max", "type": "number", "required": false, "help": "最大点数"} ], "options": [ {"name": "times", "short": "t", "type": "number", "required": false, "help": "投掷次数"} ], "handler": "on_command" // 触发时回调的 Wasm 导出函数名,默认 "on_command" } - 回调上下文:指令触发时,宿主将包含用户输入、群聊/私聊类型与环境信息的
CommandEventJSON 传给插件。若插件的导出函数直接返回非空文本,宿主会自动将其作为回复发送给用户。{ "command": "dice", "message_type": "group", // "group"(群聊)或 "direct"(私聊) "group_id": "group_12345", // 群聊 ID(私聊为空) "group": { // 群聊信息 "id": "group_12345", "name": "交流群" }, "sender_id": "user_67890", // 发送者 ID "sender": { // 发送者详情 "id": "user_67890", "name": "张三", "nickname": "三哥", "authority": 1 // 0=Banned, 1=Member, 2=GroupAdmin, 3=GroupOwner, 4=Admin, 5=Owner }, "protocol": "qbot", // 协议平台(qbot, telegram, onebot 等) "features": ["text", "image", "inline_keyboard"], "message_id": "msg_abc", // 消息唯一 ID "raw": "/dice 6", // 完整输入原始文本 "context_token": "..." // 上下文 Token,用于回传 }
3. 消息回复与主动发送(支持内嵌键盘与 Markdown)
- 回复文本消息:
gorobot_reply_text(context_token, text)- 在指令或事件处理上下文中使用收到的
context_token进行即时纯文本回复。
- 在指令或事件处理上下文中使用收到的
- 回复富消息(含内嵌键盘与 Markdown):
gorobot_reply_message(req_json)- 支持向当前上下文回复携带内嵌按钮(Inline Keyboard)或 Markdown 格式的消息(
markdown字段与text字段二选一,优先使用markdown)。
{ "context_token": "...", "markdown": "# 欢迎使用\n请选择你要执行的操作:", "keyboard": { "rows": [ [ { "text": "打开官网", "action": 0, "data": "https://example.com" }, { "text": "点击签到", "action": 2, "data": "/sign", "direct_send": true } ] ] } } - 支持向当前上下文回复携带内嵌按钮(Inline Keyboard)或 Markdown 格式的消息(
- 主动发消息:
gorobot_send_message(req_json)- 主动向指定用户或群聊发送消息,同样支持
markdown与可选的keyboard结构。
{ "context_id": "qbot:<bot_appid>", // 机器人上下文 ID,留空默认首个可用适配器 "target_id": "qbot:user&<user_openid>", // 接收者 User ID 或 Group ID "markdown": "## 系统通知\n这是来自 Wasm 插件的主动推送", "keyboard": { "rows": [ [ { "text": "赞", "action": 1, "data": "like_event" } ] ] } } - 主动向指定用户或群聊发送消息,同样支持
内嵌按钮 (Inline Keyboard) 字段说明:
action: 按钮动作类型。0=打开 URL(ActionURL),1=回调事件(ActionCallback,如 Telegram callback_data / QQ INTERACTION_CREATE),2=指令型(ActionCommand)。data: 动作对应的数据(URL 地址、回调 payload 或待执行指令)。direct_send: 布尔值,仅指令型按钮支持(为true时在支持平台直接自动发送该指令)。
4. 查询适配器支持特性 (Feature Discovery)
- 事件上下文中内置:传给插件的
CommandEvent与MessageEventPayload已内置features字段数组(例如["text", "image", "markdown", "voice", "file", "inline_keyboard"]),插件收到事件即可直接判断是否能发送内嵌按钮或 Markdown。 - 主动查询函数:
gorobot_get_features(req_json)- 支持通过当前会话
context_token或机器人context_id主动探测平台支持特性。
// 请求示例 { "context_token": "..." } // 响应示例 { "success": true, "features": ["text", "image", "markdown", "voice", "file", "inline_keyboard"] } - 支持通过当前会话
5. 监听聊天事件
- 函数名:
gorobot_subscribe_event(req_json) - 说明:订阅通用事件(如
message)。每当收到聊天消息时回调插件指定的导出函数。{ "event": "message", "handler": "on_message" } - 回调负载 (
MessageEventPayload):{ "message_type": "group", // "group" 或 "direct" "group_id": "group_12345", // 群聊 ID(私聊为空) "group": { "id": "group_12345", "name": "交流群" }, "sender_id": "user_67890", "sender": { "id": "user_67890", "nickname": "三哥", "authority": 1 }, "protocol": "telegram", "features": ["text", "image", "inline_keyboard"], "text": "测试消息内容", "timestamp": 1726272000, "context_token": "..." }
6. 网络访问(Outgoing HTTP)
- 函数名:
gorobot_http_request(req_json) - 说明:发起对外网络请求(支持 GET、POST、PUT、DELETE 等)。
- 入参示例:
{ "url": "https://api.weather.com/v1/today", "method": "GET", "headers": {"User-Agent": "GoroBot-Wasm"}, "raw_headers": {"X-Custom": ["val1", "val2"]}, // 可选:多值 Header 数组 "body": "...", // 可选:文本请求体 "body_base64": "...", // 可选:二进制请求体 Base64 编码 "timeout_ms": 5000, "follow_redirects": true // 可选:是否自动跟随重定向(默认 true) } - 完整返回响应结构 (
HttpResponsePayload):{ "status_code": 200, "status": "200 OK", "proto": "HTTP/1.1", "headers": { // 扁平化映射(支持规范键与全小写键查询) "Content-Type": "application/json; charset=utf-8", "content-type": "application/json; charset=utf-8" }, "raw_headers": { // 完整多值响应头数组 "Set-Cookie": ["sid=abc; Path=/", "token=xyz; Path=/"] }, "body": "{\"temp\": 25}", // 响应体文本内容 "body_base64": "eyJ0ZW1wIjogMjV9", // 完整响应体 Base64 编码(图片、二进制、非 UTF-8 数据 100% 完整无损) "content_length": 13, // 实际读取的响应体字节数 "error": "" // 错误信息(若有) }
7. 网络监听(Incoming HTTP / Webhook)
- 函数名:
gorobot_listen_http(req_json) - 说明:由宿主代理监听指定的 HTTP 端口与路由。当外部 Webhook 或客户端请求到达时,由宿主转发给 Wasm 插件处理,插件返回的结果自动写回 HTTP 客户端。
- 入参示例:
{ "addr": ":8080", "path": "/webhook", "handler": "on_http_request" } - 请求/响应协议:
- 外部请求送入插件导出函数格式:
{ "method": "POST", "path": "/webhook", "query": "sig=123", "headers": {"Content-Type": "application/json"}, "body": "..." } - 插件返回响应格式:
{ "status_code": 200, "headers": {"Content-Type": "application/json"}, "body": "{\"success\": true}" }
- 外部请求送入插件导出函数格式:
- 生命周期绑定:当插件被重载或禁用时,宿主自动清理路由;若该端口上无其他活跃路由,对应的 HTTP 服务将自动优雅停止。
8. 辅助文件系统操作
gorobot_fs_read(path):读取data/<plugin_id>/内的文件内容。gorobot_fs_write(path, data):写入文件到data/<plugin_id>/。- 内置防目录穿越逻辑,拦截所有试图逃逸数据目录的相对或绝对路径。
9. 定时器与周期性轮询任务 (Timers & Polling)
由于 Wasm 为单线程沙箱模型,不能在插件中直接执行死循环或睡眠。宿主为 Wasm 插件提供了后台协程驱动的定时调度能力:
- 设置周期性轮询定时器:
gorobot_set_interval(req_json)- 向宿主申请启动一个周期定时器,定时调用插件指定的导出函数。
- 入参格式:
{ "interval_ms": 60000, // 触发间隔(毫秒),必须大于 0 "handler": "on_poll", // 触发时调用的 Wasm 导出函数名,默认 "on_interval" "payload": "optional_data" // 可选,传递给处理函数的自定义字符串 } - 响应格式:
{ "success": true, "timer_id": "timer_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }
- 设置单次延时任务:
gorobot_set_timeout(req_json)- 延时执行一次插件指定的导出函数(默认函数名为 "on_timeout")。
- 入参格式:
{ "delay_ms": 5000, "handler": "on_delay", "payload": "optional_data" }
- 清除/取消定时器:
gorobot_clear_timer(req_json)- 传入
timer_id取消正在运行的定时器或延时任务(亦支持gorobot_clear_interval与gorobot_clear_timeout别名)。 - 入参格式:
{"timer_id": "..."}
- 传入
- 获取系统当前时间:
gorobot_time_now()- 返回当前系统时间戳:
{"unix": 1726300000, "unix_milli": 1726300000000, "iso8601": "..."}。
- 返回当前系统时间戳:
- 生命周期绑定:当插件被禁用、重载或卸载时,宿主会自动取消并清理该插件注册的所有未执行定时器,避免协程泄露。
用户权限与身份体系
宿主在向 Wasm 插件传递指令事件(CommandEvent)和消息事件(MessageEventPayload)时,会自动解析并注入发送者的身份与权限信息:sender.authority。
1. 权限等级对应表 (Authority)
底层对应 GoroBot 核心实体 entity.Authority(整型数值 0 ~ 5):
| 数值 | 权限标识 | 对应身份 | 说明 |
|---|---|---|---|
| 0 | Banned | 被封禁用户 | 黑名单用户,通常无权触发任何正常交互 |
| 1 | Member | 普通成员 | 群聊普通成员或普通私聊用户(默认值) |
| 2 | GroupAdmin | 群管理员 | 当前群的管理员(仅群聊场景有效) |
| 3 | GroupOwner | 群主 | 当前群的创建者/群主(仅群聊场景有效) |
| 4 | Admin | 机器人管理员 | 被配置为 GoroBot 管理员的用户 |
| 5 | Owner | 机器人所有者 | 机器人最高所有者(拥有全部指令与管理权限) |
2. 上下文数据结构
无论在指令回调还是消息事件回调中,均可通过 sender 对象获取:
{
"sender_id": "user_67890",
"sender": {
"id": "user_67890",
"name": "张三",
"nickname": "三哥",
"avatar": "https://...",
"authority": 2
}
}
3. 插件内鉴权示例(Rust)
#![allow(unused)] fn main() { use serde::Deserialize; #[derive(Deserialize)] struct SenderInfo { id: String, nickname: Option<String>, #[serde(default)] authority: i32, } #[derive(Deserialize)] struct CommandContext { sender: Option<SenderInfo>, raw: String, } // 检查是否具备群管及以上权限 fn is_admin(ctx: &CommandContext) -> bool { ctx.sender.as_ref().map_or(false, |s| s.authority >= 2) } // 检查是否为机器人最高所有者 fn is_owner(ctx: &CommandContext) -> bool { ctx.sender.as_ref().map_or(false, |s| s.authority >= 5) } }
插件编写示例(Rust 版)
使用 Rust 配合 extism-pdk 编写一个完整的 Wasm 插件:
#![allow(unused)] fn main() { use extism_pdk::*; use serde::{Deserialize, Serialize}; #[host_fn] extern "ExtismHost" { fn gorobot_log(level: &str, msg: &str); fn gorobot_register_command(def_json: &str) -> String; } #[derive(Serialize)] struct CommandDef { name: String, description: String, handler: String, } #[derive(Deserialize)] struct CommandContext { sender_id: String, raw: String, } // 插件加载时自动执行的初始化钩子 #[plugin_fn] pub fn on_init() -> FnResult<()> { unsafe { gorobot_log("info", "Dice 插件正在加载...")?; let def = CommandDef { name: "dice".to_string(), description: "掷骰子".to_string(), handler: "on_dice".to_string(), }; let _ = gorobot_register_command(&serde_json::to_string(&def)?)?; } Ok(()) } // 指令触发时的回调函数 #[plugin_fn] pub fn on_dice(Json(ctx): Json<CommandContext>) -> FnResult<String> { // 读取与写入插件独占的数据目录文件 let counter_file = "/counter.txt"; let count: u32 = std::fs::read_to_string(counter_file) .unwrap_or_else(|_| "0".to_string()) .trim() .parse() .unwrap_or(0) + 1; let _ = std::fs::write(counter_file, count.to_string()); // 返回的文本会自动回复给发送者 Ok(format!("用户 {} 掷出了 6 点!本插件已累计被调用 {} 次。", ctx.sender_id, count)) } }
编译生成 .wasm 文件并放置到 GoroBot 的 plugin/ 目录:
cargo build --target wasm32-wasip1 --release
cp target/wasm32-wasip1/release/dice.wasm path/to/GoroBot/plugin/dice.wasm
在聊天窗口发送 /wasm lookup 与 /wasm enable dice 即可立即启用!
API 文档
目前项目正处于开发初期,api 及其不稳定,建议参照
示例插件中的使用方法。
- GoroBot.Resource 统一资源文件管理
- GoroBot.Database 数据库操作
- 消息类型 消息上下文、消息结构、消息构建器
资源文件管理
位于 pkg/core/resource.go,是 GoroBot 实例下的方法,为统一管理资源文件提供了接口。
文件存放于工作目录的 resources/ 下。
Resource
- ID
string资源唯一标识符 - Protocol
string来源协议(适配器 context ID) - RefLink
string协议特定的资源引用链接 - FilePath
string资源文件本地路径 - Downloaded
time.Time资源记录时间
grb.SaveResourceLink(contextID string, refLink string) string
保存资源引用链接,返回生成的资源 ID。此时并不会下载文件,而是等到 LoadResourceFromID 时再按需下载。
grb.LoadResourceFromID(id string) (string, error)
根据资源 ID 获取本地文件路径。如果文件还没下载,会通过对应适配器的 DownloadResourceFromRefLink 下载并缓存。
数据库操作
GoroBot 实例中存放着一个数据库接口。暴露了几个方法用于简易连接与操作。
grb.OpenDatabase(driverName string, dataSourceName string) error
打开数据库。同 sql.Open(driverName, dataSourceName)。
grb.CloseDatabase() error
关闭数据库。
grb.Database() *sql.DB
获取数据库实例。
grb.DatabaseExist() bool
如果连接了数据库,返回 true,否则返回 false。
消息类型
消息相关的类型位于 pkg/core/bot_context,包含消息上下文和消息结构。
MessageContext
消息的上下文,由适配器在触发消息事件时提供。
ctx.Protocol() string
消息的协议,值可以为机器人所处的平台或所使用的协议。
ctx.BotContext() BotContext
获取所在平台的适配器上下文。
ctx.String() string
返回消息的文本预览。
ctx.Message() *BaseMessage
返回消息结构。
ctx.SenderID() string
返回发送者 ID。
ctx.NewMessageBuilder() MessageBuilder
创建消息构建器。
ctx.Reply(elements []*MessageElement) (*BaseMessage, error)
在当前上下文中回复消息。具体实现由适配器决定。
ctx.ReplyText(a ...any) (*BaseMessage, error)
回复纯文本消息的便捷方法。
BaseMessage
消息的基本结构。含有以下属性:
- MessageType
MessageType消息类型(DirectMessage/GroupMessage) - ID
string消息 ID - Content
string显示文本 - Elements
[]*MessageElement消息元素 - Sender
*entity.Sender消息发送者 - Time
time.Time消息发送(接收)时间
*BaseMessage.Marshall() string
序列化消息实体,使其能被保存以及使用 UnmarshallMessage(string) 读取。
MessageElement
消息元素,消息的基本组成部分。
- Type
ElementType元素类型 - Content
string显示文本 - Source
string多媒体索引
TextElement
- Content:
显示文本 - Source:
""
QuoteElement
- Content:
[回复] - Source:
被引用消息的序列化对象
MentionElement
- Content:
@Somebody - Source:
protocol:被提及用户的ID
ImageElement
- Content:
[图片] - Source:
资源ID
VideoElement
- Content:
[视频] - Source:
资源ID
FileElement
- Content:
[文件] - Source:
protocol:参数...
VoiceElement
- Content:
[语音] - Source:
资源ID
StickerElement
- Content:
[表情] - Source:
protocol:参数...
LinkElement
- Content:
百度一下,你就知道 - Source:
https://www.baidu.com
InlineKeyboardElement
- Content:
内嵌键盘 JSON 序列化字符串 - Source:
""
MarkdownElement
- Content:
Markdown 格式文本内容 - Source:
""
OtherElement
- Content:
奇怪的东西 - Source:
protocol:参数
MessageBuilder
构建和发送消息的链式 API。通过 ctx.NewMessageBuilder() 或 ctx.BotContext().NewMessageBuilder() 创建。
builder.Text(text string) MessageBuilder
添加普通文本内容。
builder.Markdown(content string) MessageBuilder
添加 Markdown 格式内容(在支持 Markdown 的适配器上如 QBot、Telegram 将按 Markdown 渲染发送,其他平台自动回退为纯文本)。
builder.InlineKeyboard(kb *InlineKeyboard) MessageBuilder
添加内嵌键盘按钮组件(在支持 Inline Keyboard 的平台展示交互按钮)。
builder.ImageFromFile(path string) MessageBuilder
从本地文件添加图片。
builder.ImageFromUrl(url string) MessageBuilder
从 URL 添加图片。
builder.ImageFromData(data []byte) MessageBuilder
从二进制数据添加图片。
builder.Quote(msg *BaseMessage) MessageBuilder
引用一条消息。
builder.Mention(id string) MessageBuilder
@某人。
builder.ReplyTo(msg MessageContext) (*BaseMessage, error)
作为回复发送到消息来源。
builder.Send(id string) (*BaseMessage, error)
发送到指定的聊天 ID。
BaseBuilder
构建消息元素链的工具,用于适配器内部解析消息。
NewBuilder() *BaseBuilder
创建构建器。
*BaseBuilder.Append(elementType ElementType, content string, source string) *BaseBuilder
在消息链中添加元素。
*BaseBuilder.Build() []*MessageElement
返回构造完成的元素链。
插件列表
欢迎在 issue 中提供你所写的插件信息。信息将会在维护者查看后加入列表。
注意事项
重申:如果有能力,请审查所有使用的代码!!! 由于语言的特性,开发者拥有极高的权限,请在使用不可信来源的插件时多加注意!
恶意插件可以做到的事情有:
- 向恶意者发送消息
- 发送恶意消息
- 向恶意者传送账号信息以盗取账号
- 盗取你的本机账号
插件
消息日志
import "github.com/Jel1ySpot/GoroBot/example_plugin/message_logger"
基础的消息事件监听。将消息预览打印到日志。
Ping
import "github.com/Jel1ySpot/GoroBot/example_plugin/ping"
检测机器人连接状态。发送 ping 或 /ping 回复 🏓。
骰子
import "github.com/Jel1ySpot/GoroBot/example_plugin/dice/dice"
掷骰子。支持 /dice 20 或别名 d20 的写法。
插件载入
import "github.com/Jel1ySpot/GoroBot/example_plugin/go_plugin"
载入外部 .so 插件,支持加载/卸载/启用/禁用。限类 Unix 系统。
Wasm 插件引擎
import "github.com/Jel1ySpot/GoroBot/pkg/wasm_plugin"
基于 Extism (wazero) 构建的跨平台安全沙箱插件系统。支持 Rust/TinyGo 等多语言编写,支持真正的运行时热重载、完全卸载与网络/文件系统权限控制。详见 Wasm 插件系统。
调试工具
import "github.com/Jel1ySpot/GoroBot/example_plugin/tests"
一些调试用的命令:/repr(输出消息 JSON)、/getOwner、/getResourceFromID、/sendImage。