mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-19 05:30:07 +08:00
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
302 lines
11 KiB
Go
302 lines
11 KiB
Go
// Package minecraftobserver 通过mc服务器地址获取服务器状态信息并绘制图片发送到QQ群
|
||
package minecraftobserver
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
ctrl "github.com/FloatTech/zbpctrl"
|
||
"github.com/FloatTech/zbputils/control"
|
||
zbpCtxExt "github.com/FloatTech/zbputils/ctxext"
|
||
zero "github.com/wdvxdr1123/ZeroBot"
|
||
"github.com/wdvxdr1123/ZeroBot/message"
|
||
)
|
||
|
||
const (
|
||
name = "minecraftobserver"
|
||
)
|
||
|
||
var (
|
||
// 注册插件
|
||
engine = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
|
||
// 默认不启动
|
||
DisableOnDefault: false,
|
||
Brief: "Minecraft服务器状态查询/订阅",
|
||
// 详细帮助
|
||
Help: "- mc服务器状态 [服务器IP/URI]\n" +
|
||
"- mc服务器添加订阅 [服务器IP/URI]\n" +
|
||
"- mc服务器取消订阅 [服务器IP/URI]\n" +
|
||
"- mc服务器订阅拉取 (需要插件定时任务配合使用,全局只需要设置一个)" +
|
||
"-----------------------\n" +
|
||
"使用job插件设置定时, 例:" +
|
||
"记录在\"@every 1m\"触发的指令\n" +
|
||
"(机器人回答:您的下一条指令将被记录,在@@every 1m时触发)" +
|
||
"mc服务器订阅拉取",
|
||
// 插件数据存储路径
|
||
PrivateDataFolder: name,
|
||
}).ApplySingle(zbpCtxExt.DefaultSingle)
|
||
)
|
||
|
||
func init() {
|
||
// 状态查询
|
||
engine.OnRegex("^[mM][cC]服务器状态 (.+)$").SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||
// 关键词查找
|
||
addr := ctx.State["regex_matched"].([]string)[1]
|
||
resp, err := getMinecraftServerStatus(addr)
|
||
if err != nil {
|
||
ctx.Send(message.Text("服务器状态获取失败... 错误信息: ", err))
|
||
return
|
||
}
|
||
status := resp.genServerSubscribeSchema(addr, 0)
|
||
textMsg, iconBase64 := status.generateServerStatusMsg()
|
||
var msg message.Message
|
||
if iconBase64 != "" {
|
||
msg = append(msg, message.Image(iconBase64))
|
||
}
|
||
msg = append(msg, message.Text(textMsg))
|
||
if id := ctx.Send(msg); id.ID() == 0 {
|
||
// logrus.Errorln(logPrefix + "Send failed")
|
||
return
|
||
}
|
||
})
|
||
// 添加订阅
|
||
engine.OnRegex(`^[mM][cC]服务器添加订阅\s*(.+)$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||
// 关键词查找
|
||
addr := ctx.State["regex_matched"].([]string)[1]
|
||
status, err := getMinecraftServerStatus(addr)
|
||
if err != nil {
|
||
ctx.Send(message.Text("服务器信息初始化失败,请检查服务器是否可用!\n错误信息: ", err))
|
||
return
|
||
}
|
||
targetID, targetType := warpTargetIDAndType(ctx.Event.GroupID, ctx.Event.UserID)
|
||
err = dbInstance.newSubscribe(addr, targetID, targetType)
|
||
if err != nil {
|
||
ctx.Send(message.Text("订阅添加失败... 错误信息: ", err))
|
||
return
|
||
}
|
||
// 插入数据库(首条,需要更新状态)
|
||
err = dbInstance.updateServerStatus(status.genServerSubscribeSchema(addr, 0))
|
||
if err != nil {
|
||
ctx.Send(message.Text("服务器状态更新失败... 错误信息: ", err))
|
||
return
|
||
}
|
||
if sid := ctx.Send(message.Text(fmt.Sprintf("服务器 %s 订阅添加成功", addr))); sid.ID() == 0 {
|
||
// logrus.Errorln(logPrefix + "Send failed")
|
||
return
|
||
}
|
||
// 成功后立即发送一次状态
|
||
textMsg, iconBase64 := status.genServerSubscribeSchema(addr, 0).generateServerStatusMsg()
|
||
var msg message.Message
|
||
if iconBase64 != "" {
|
||
msg = append(msg, message.Image(iconBase64))
|
||
}
|
||
msg = append(msg, message.Text(textMsg))
|
||
if id := ctx.Send(msg); id.ID() == 0 {
|
||
// logrus.Errorln(logPrefix + "Send failed")
|
||
return
|
||
}
|
||
})
|
||
// 删除
|
||
engine.OnRegex(`^[mM][cC]服务器取消订阅\s*(.+)$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||
addr := ctx.State["regex_matched"].([]string)[1]
|
||
// 通过群组id和服务器地址获取服务器状态
|
||
targetID, targetType := warpTargetIDAndType(ctx.Event.GroupID, ctx.Event.UserID)
|
||
err := dbInstance.deleteSubscribe(addr, targetID, targetType)
|
||
if err != nil {
|
||
ctx.Send(message.Text("取消订阅失败...", fmt.Sprintf("错误信息: %v", err)))
|
||
return
|
||
}
|
||
ctx.Send(message.Text("取消订阅成功"))
|
||
})
|
||
// 查看当前渠道的所有订阅
|
||
engine.OnRegex(`^[mM][cC]服务器订阅列表$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||
subList, err := dbInstance.getSubscribesByTarget(warpTargetIDAndType(ctx.Event.GroupID, ctx.Event.UserID))
|
||
if err != nil {
|
||
ctx.Send(message.Text("获取订阅列表失败... 错误信息: ", err))
|
||
return
|
||
}
|
||
if len(subList) == 0 {
|
||
ctx.Send(message.Text("当前没有订阅哦"))
|
||
return
|
||
}
|
||
stringBuilder := strings.Builder{}
|
||
stringBuilder.WriteString("[订阅列表]\n")
|
||
for _, v := range subList {
|
||
stringBuilder.WriteString(fmt.Sprintf("服务器地址: %s\n", v.ServerAddr))
|
||
}
|
||
if sid := ctx.Send(message.Text(stringBuilder.String())); sid.ID() == 0 {
|
||
// logrus.Errorln(logPrefix + "Send failed")
|
||
return
|
||
}
|
||
})
|
||
// 查看全局订阅情况(仅限管理员私聊可用)
|
||
engine.OnRegex(`^[mM][cC]服务器全局订阅列表$`, zero.OnlyPrivate, zero.SuperUserPermission, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||
subList, err := dbInstance.getAllSubscribes()
|
||
if err != nil {
|
||
ctx.Send(message.Text("获取全局订阅列表失败... 错误信息: ", err))
|
||
return
|
||
}
|
||
if len(subList) == 0 {
|
||
ctx.Send(message.Text("当前一个订阅都没有哦"))
|
||
return
|
||
}
|
||
userID := ctx.Event.UserID
|
||
userName := ctx.CardOrNickName(userID)
|
||
msg := make(message.Message, 0)
|
||
|
||
// 按照群组or用户分组来定
|
||
groupSubMap := make(map[int64][]serverSubscribe)
|
||
userSubMap := make(map[int64][]serverSubscribe)
|
||
for _, v := range subList {
|
||
switch v.TargetType {
|
||
case targetTypeGroup:
|
||
groupSubMap[v.TargetID] = append(groupSubMap[v.TargetID], v)
|
||
case targetTypeUser:
|
||
userSubMap[v.TargetID] = append(userSubMap[v.TargetID], v)
|
||
default:
|
||
}
|
||
}
|
||
|
||
// 群
|
||
for k, v := range groupSubMap {
|
||
stringBuilder := strings.Builder{}
|
||
stringBuilder.WriteString(fmt.Sprintf("[群 %d]存在以下订阅:\n", k))
|
||
for _, sub := range v {
|
||
stringBuilder.WriteString(fmt.Sprintf("服务器地址: %s\n", sub.ServerAddr))
|
||
}
|
||
msg = append(msg, message.CustomNode(userName, userID, stringBuilder.String()))
|
||
}
|
||
// 个人
|
||
for k, v := range userSubMap {
|
||
stringBuilder := strings.Builder{}
|
||
stringBuilder.WriteString(fmt.Sprintf("[用户 %d]存在以下订阅:\n", k))
|
||
for _, sub := range v {
|
||
stringBuilder.WriteString(fmt.Sprintf("服务器地址: %s\n", sub.ServerAddr))
|
||
}
|
||
msg = append(msg, message.CustomNode(userName, userID, stringBuilder.String()))
|
||
}
|
||
// 合并发送
|
||
ctx.SendPrivateForwardMessage(ctx.Event.UserID, msg)
|
||
})
|
||
// 状态变更通知,全局触发,逐个服务器检查,检查到变更则逐个发送通知
|
||
engine.OnRegex(`^[mM][cC]服务器订阅拉取$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
|
||
serverList, err := dbInstance.getAllSubscribes()
|
||
if err != nil {
|
||
su := zero.BotConfig.SuperUsers[0]
|
||
// 如果订阅列表获取失败,通知管理员
|
||
ctx.SendPrivateMessage(su, message.Text(logPrefix, "获取订阅列表失败..."))
|
||
return
|
||
}
|
||
// logrus.Debugln(logPrefix+"global get ", len(serverList), " subscribe(s)")
|
||
serverMap := make(map[string][]serverSubscribe)
|
||
for _, v := range serverList {
|
||
serverMap[v.ServerAddr] = append(serverMap[v.ServerAddr], v)
|
||
}
|
||
changedCount := 0
|
||
for subAddr, oneServerSubList := range serverMap {
|
||
// 查询当前存储的状态
|
||
storedStatus, sErr := dbInstance.getServerStatus(subAddr)
|
||
if sErr != nil {
|
||
// logrus.Errorln(logPrefix+fmt.Sprintf("getServerStatus ServerAddr(%s) error: ", subAddr), sErr)
|
||
continue
|
||
}
|
||
isChanged, changedNotifyMsg, sErr := singleServerScan(storedStatus)
|
||
if sErr != nil {
|
||
// logrus.Errorln(logPrefix+"singleServerScan error: ", sErr)
|
||
continue
|
||
}
|
||
if !isChanged {
|
||
continue
|
||
}
|
||
changedCount++
|
||
// 发送变化信息
|
||
for _, subInfo := range oneServerSubList {
|
||
time.Sleep(100 * time.Millisecond)
|
||
if subInfo.TargetType == targetTypeUser {
|
||
ctx.SendPrivateMessage(subInfo.TargetID, changedNotifyMsg)
|
||
} else if subInfo.TargetType == targetTypeGroup {
|
||
m, ok := control.Lookup(name)
|
||
if !ok {
|
||
continue
|
||
}
|
||
if !m.IsEnabledIn(subInfo.TargetID) {
|
||
continue
|
||
}
|
||
ctx.SendGroupMessage(subInfo.TargetID, changedNotifyMsg)
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// singleServerScan 单个服务器状态扫描
|
||
func singleServerScan(oldSubStatus *serverStatus) (changed bool, notifyMsg message.Message, err error) {
|
||
notifyMsg = make(message.Message, 0)
|
||
newSubStatus := &serverStatus{}
|
||
// 获取服务器状态 & 检查是否需要更新
|
||
rawServerStatus, err := getMinecraftServerStatus(oldSubStatus.ServerAddr)
|
||
if err != nil {
|
||
// logrus.Warnln(logPrefix+"getMinecraftServerStatus error: ", err)
|
||
err = nil
|
||
// 计数器没有超限,增加计数器并跳过
|
||
if cnt, ts := addPingServerUnreachableCounter(oldSubStatus.ServerAddr, time.Now()); cnt < pingServerUnreachableCounterThreshold &&
|
||
time.Since(ts) < pingServerUnreachableCounterTimeThreshold {
|
||
// logrus.Warnln(logPrefix+"server ", oldSubStatus.ServerAddr, " unreachable, counter: ", cnt, " ts:", ts)
|
||
return
|
||
}
|
||
// 不可达计数器已经超限,则更新服务器状态
|
||
// 深拷贝,设置PingDelay为不可达
|
||
newSubStatus = oldSubStatus.deepCopy()
|
||
newSubStatus.PingDelay = pingDelayUnreachable
|
||
} else {
|
||
newSubStatus = rawServerStatus.genServerSubscribeSchema(oldSubStatus.ServerAddr, oldSubStatus.ID)
|
||
}
|
||
if newSubStatus == nil {
|
||
// logrus.Errorln(logPrefix + "newSubStatus is nil")
|
||
return
|
||
}
|
||
// 检查是否有订阅信息变化
|
||
if oldSubStatus.isServerStatusSpecChanged(newSubStatus) {
|
||
// logrus.Warnf(logPrefix+"server subscribe spec changed: (%+v) -> (%+v)", oldSubStatus, newSubStatus)
|
||
changed = true
|
||
// 更新数据库
|
||
err = dbInstance.updateServerStatus(newSubStatus)
|
||
if err != nil {
|
||
// logrus.Errorln(logPrefix+"updateServerSubscribeStatus error: ", err)
|
||
return
|
||
}
|
||
// 纯文本信息
|
||
notifyMsg = append(notifyMsg, message.Text(formatSubStatusChangeText(oldSubStatus, newSubStatus)))
|
||
// 如果有图标变更
|
||
if oldSubStatus.FaviconMD5 != newSubStatus.FaviconMD5 {
|
||
// 有图标变更
|
||
notifyMsg = append(notifyMsg, message.Text("\n-----[图标变更]-----\n"))
|
||
// 旧图标
|
||
notifyMsg = append(notifyMsg, message.Text("[旧]\n"))
|
||
if oldSubStatus.FaviconRaw != "" {
|
||
notifyMsg = append(notifyMsg, message.Image(oldSubStatus.FaviconRaw.toBase64String()))
|
||
} else {
|
||
notifyMsg = append(notifyMsg, message.Text("(空)\n"))
|
||
}
|
||
// 新图标
|
||
notifyMsg = append(notifyMsg, message.Text("[新]\n"))
|
||
if newSubStatus.FaviconRaw != "" {
|
||
notifyMsg = append(notifyMsg, message.Image(newSubStatus.FaviconRaw.toBase64String()))
|
||
} else {
|
||
notifyMsg = append(notifyMsg, message.Text("(空)\n"))
|
||
}
|
||
}
|
||
notifyMsg = append(notifyMsg, message.Text("\n-------最新状态-------\n"))
|
||
// 服务状态
|
||
textMsg, iconBase64 := newSubStatus.generateServerStatusMsg()
|
||
if iconBase64 != "" {
|
||
notifyMsg = append(notifyMsg, message.Image(iconBase64))
|
||
}
|
||
notifyMsg = append(notifyMsg, message.Text(textMsg))
|
||
}
|
||
// 逻辑到达这里,说明状态已经变更 or 无变更且服务器可达,重置不可达计数器
|
||
resetPingServerUnreachableCounter(oldSubStatus.ServerAddr)
|
||
return
|
||
}
|