- 基本完善所有事件和 API,并支持同时运行多个实例(支持同端口、同路径运行多个拥有不同监听器的机器人)
- 特别针对消息类型事件,配有 OnCommand、Preprocessor、Reply、WaifForCommand 等拓展处理器
- 具备 Plugins 模块,允许使用外部模块直接编写应用
- 内置消息过滤器,自动过滤重复消息
- 底层使用gin构建,允许加入自定义路由(内部机制处理同端口同路径等复杂情况),方便集成页面应用供机器人使用(并校验用户)
- 支持http和ws的反向代理配置和使用,具体请参考example5
go version >= 1.18(要求来自gin)
实例
请参阅examples
- example1:简单的机器人,包含了消息处理器、指令处理器、事件监听器
- example2:多机器人同时允许
- example3:简单的插件结构
- example4:机器人暂停当前消息处理链,等待用户输入(以简单的猜数字机器人为例子,修改bot id等机器人参数后可直接使用)
- example5:基于反向代理的实例
实用工具
请参阅tools
- getAllVilla:获取机器人所在的全部别野
由于官方文档与实际存在不少差异,目前并不能确保所有消息事件和 API 完全正确
Import
"github.com/GLGDLY/mhy_botsdk/bot"
:机器人基础模块,包含机器人实例、事件监听器、消息处理器等"github.com/GLGDLY/mhy_botsdk/events"
:事件模块,包含所有事件的模型"github.com/GLGDLY/mhy_botsdk/apis"
:API 模块,包含所有 API 的处理器"github.com/GLGDLY/mhy_botsdk/api_models"
:API 模块,包含所有 API 的模型"github.com/GLGDLY/mhy_botsdk/commands"
:指令模块,包含指令处理器"github.com/GLGDLY/mhy_botsdk/plugins"
:插件模块,包含插件处理器"github.com/GLGDLY/mhy_botsdk/utils"
:辅助工具模块,包含一些实用函数
package main
import (
"strings"
bot_api_models "github.com/GLGDLY/mhy_botsdk/api_models"
bot_base "github.com/GLGDLY/mhy_botsdk/bot"
bot_commands "github.com/GLGDLY/mhy_botsdk/commands"
bot_events "github.com/GLGDLY/mhy_botsdk/events"
)
// NewBot参数: id, secret, 路径, 端口
// 下方例子会监听 localhost:8888/ 获取消息
// 并验证事件的机器人ID是否符合
var bot = bot_base.NewBot("bot_id", "bot_secret", "bot_pubkey", "/", ":8888")
func msg_preprocessor(data bot_events.EventSendMessage) { // 借助preprocessor为所有消息记录log
bot.Logger.Info("收到来自 " + data.Data.Nickname + " 的消息:" + data.GetContent(true))
}
func MyCommand1(data bot_events.EventSendMessage) {
bot.Logger.Info("MyCommand1")
reply, _ := bot_api_models.NewMsg(bot_api_models.MsgTypeImage) // 创建图片类型的消息体
// 设置图片消息内容
reply.SetImage("https://webstatic.mihoyo.com/vila/bot/doc/message_api/img/text_case.jpg")
bot.Logger.Info(data.ReplyCustomize(reply))
// 设置本地图片消息内容
resp, http_code, err := bot.Api.UploadFileImage(data.Robot.VillaId, "head.jpg")
if err != nil {
bot.Logger.Error(err)
reply := fmt.Sprintf("上传图片失败,错误信息(%d):%v", http_code, err)
bot.Logger.Info(data.Reply(reply))
return
}
reply.SetImage(resp.Data.NewURL)
bot.Logger.Info(data.ReplyCustomize(reply))
}
func MyCommand2(data bot_events.EventSendMessage) {
bot.Logger.Info("MyCommand2")
bot.Logger.Info(data.Reply(fmt.Sprintf("MyCommand2 <@%v> <@%v> <@everyone> <#%v>",
data.Robot.Template.Id, data.Data.FromUserId, data.Data.RoomId))) // 使用内嵌格式发送文本消息,内嵌格式按顺序为:@机器人(自己)、@发送者、@全体、#跳转房间
}
func msg_handler(data bot_events.EventSendMessage) { // 最后触发监听器,一般用于确保任何消息都有回复
bot.Logger.Info("default msg handler")
reply, _ := bot_api_models.NewMsg(bot_api_models.MsgTypeText) // 创建文本类型的消息体
if strings.Contains(data.GetContent(true), "hello") { // 判断消息内容是否包含 "hello"
reply.SetText("Hello World!",
bot_api_models.MsgEntityMentionUser{ // 为回复的消息加入@发送者的消息
Text: "@" + data.Data.Nickname,
UserID: data.Data.FromUserId,
},
"\n",
)
reply.AppendText(bot_api_models.MsgEntityVillaRoomLink{ // 为回复的消息加入大别野房间链接
Text: "#跳转房间",
VillaID: data.Robot.VillaId,
RoomID: data.Data.RoomId,
})
reply.AppendText(bot_api_models.MsgEntityMentionAll{
Text: "@全体成员",
})
bot.Logger.Info(data.ReplyCustomize(reply))
} else {
reply.SetText("你好,我是机器人,你可以输入 hello 来和我",
bot_api_models.MsgEntityMentionRobot{ // 艾特机器人
Text: "@" + data.Robot.Template.Name ,
BotID: data.Robot.Template.Id,
}, " 打招呼")
bot.Logger.Info(data.ReplyCustomize(reply))
}
}
func main() {
bot.AddPreprocessor(msg_preprocessor)
bot.AddOnCommand(bot_commands.OnCommand{
Command: []string{"MyCommand1", "hello world"}, // 命令匹配:包含 "MyCommand1" 或 "hello world" 的消息
Listener: MyCommand1, // 设置回调函数为 MyCommand1
RequireAT: true, // 是否需要 @ 机器人才能触发
RequireAdmin: false, // 是否需要管理员权限才能触发
IsShortCircuit: true, // 是否短路,即触发后不再继续匹配后续指令和监听器
})
bot.AddOnCommand(bot_commands.OnCommand{
Regex: "/?MyCommand2", // 正则匹配:内容为 "MyCommand2" 或 "/MyCommand2" 的消息
Listener: MyCommand2,
RequireAT: true,
RequireAdmin: false,
IsShortCircuit: true,
})
bot.AddListenerSendMessage(msg_handler)
/* NewBot 创建一个机器人实例,bot_id 为机器人的id,bot_secret 为机器人的secret,path 为接收事件的路径(如"/"),addr 为接收事件的地址(如":8888");
* 机器人实例创建后,需要调用 Run() 方法启动机器人;
* 对于消息处理,可以通过 AddPreprocessor() 方法添加预处理器,通过 AddOnCommand() 方法添加命令处理器,通过 AddListener() 方法添加事件监听器;
* 对于插件,可以通过 AddPlugin() 方法添加插件;
* 整体消息处理的运行与短路顺序为: [main]预处理器 -> [插件]预处理器 -> [插件]令处理器 -> [main]命令处理器 -> [main]事件监听器;
* 如以上例子,如输入"hello world",将会执行MyCommand1,然后短路,不执行msg_handler的"hello"指令;而如果输入"hello 123",则会执行msg_handler的"hello"指令 */
bot_base.StartAll() // 开始运行所有机器人和 HTTP 服务器
}
- SDK 的事件类型模型存放在"github.com/GLGDLY/mhy_botsdk/events"中
- 事件类型 EventType 分为 6 种 event:
JoinVilla
,SendMessage
,CreateRobot
,DeleteRobot
,AddQuickEmoticon
,AuditCallback
AddListener
的注册监听器也相应分为了 6 种:AddListenerJoinVilla
,AddListenerSendMessage
,AddListenerCreateRobot
,AddListenerDeleteRobot
,AddListenerAddQuickEmoticon
,AddListenerAuditCallback
- 事件数据结构 Event 细分成 6 个子事件:
EventJoinVilla
,EventSendMessage
,EventCreateRobot
,EventDeleteRobot
,EventAddQuickEmoticon
,EventAuditCallback
- 事件数据结构将作为参数传入注册的事件回调函数
- 由于本 SDK 针对不同事件,设置了不同的消息监听器函数接口,我们得以减少官方事件数据中 extend_data 的“套娃”设计,事件的数据结构,原来的
Event
->extend_data
->event_data
->JoinVilla
/SendMessage
....将简化为Event
->Data
,Data
下直接包含各个事件的扩展数据
-
API 基本遵从官方 API 的结构,但存在特例:
-
SendMessage
(EventSendMessage
中Reply
为对其的包装器):传入string类型的参数,会自动解析其中的内嵌格式并转换为entity:<@xxx>
为艾特机器人或用户,<@everyone>
为艾特全体,<#xxx>
为跳转房间,<$xxx>
为跳转连接- 艾特用户会自动获取用户昵称,跳转房间会自动获取房间名称;艾特机器人会显示文字“@机器人”,艾特全体会显示“@全体成员”,跳转连接会显示链接自身
-
SendMessageCustomize
(EventSendMessage
中ReplyCustomize
为对其的包装器):最后一个 msg 参数要求使用"github.com/GLGDLY/mhy_botsdk/api_models"中的NewMsg
构造并传入NewMsg
需要传入MsgTypeText
,MsgTypeImage
,MsgTypePost
之一指定类型NewMsg
会返回一个MsgInputModel
结构,其中包含仅限MsgTypeText
的方法:AppendText
,SetText
,SetTextQuote
;仅限MsgTypeImage
的方法:SetImage
;仅限MsgTypePost
的方法:SetPost
- 这种设计模式是为了分段式内部处理
entities
,方便用户无需执行配置消息 json 序列
-
Audit
:最后一个参数要求传入"github.com/GLGDLY/mhy_botsdk/api_models"中的UserInputAudit
结构体- 方便处理可选参数
-
- 插件的 OnCommand 回调函数会增加一个 AbstractBot 参数,以使用当前机器人的基础功能,如 API、Logger、WaitForCommand 等
- 以下分为两个文件,其中
plugin1.go
为介绍插件的编写,my_bot.go
为介绍加载插件
package plugin1
import (
bot_api_models "github.com/GLGDLY/mhy_botsdk/api_models"
bot_events "github.com/GLGDLY/mhy_botsdk/events"
bot_plugins "github.com/GLGDLY/mhy_botsdk/plugins"
)
func command1(data bot_events.EventSendMessage, bot *bot_plugins.AbstractBot) {
bot.Logger.Info("plugin1::command1")
bot.Logger.Info(data.Reply("plugin1::command1"))
}
func command2(data bot_events.EventSendMessage, bot *bot_plugins.AbstractBot) {
bot.Logger.Info("plugin1::command2")
bot.Logger.Info(data.Reply("plugin1::command2"))
}
func init() {
bot_plugins.RegisterPlugin( // 注册插件
"plugin1", // 插件名
&bot_plugins.Plugin{ // 插件内容
OnCommand: []bot_plugins.OnCommand{
{
Command: []string{"command1"},
Listener: command1,
RequireAT: true,
RequireAdmin: false,
IsShortCircuit: true,
},
{
Command: []string{"command2"},
Listener: command2,
RequireAT: true,
RequireAdmin: false,
IsShortCircuit: true,
},
},
},
)
}
package main
import (
bot_base "github.com/GLGDLY/mhy_botsdk/bot"
_ "path_to_plugin1" // 使用import导入plugin1
)
// 创建NewBot时会自动加载import了的插件
var bot = bot_base.NewBot("bot_id", "bot_secret", "bot_pubkey", "/", ":8888")
func main() {
bot.SetPluginsShortCircuitAffectMain(true) // 设置插件的短路是否影响main中注册的指令和消息处理器
bot_base.StartAll() // 开始运行所有机器人和 HTTP 服务器
}