mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2026-03-02 02:50:27 +00:00
feat: add plugin Minecraft 服务器状态查询 (#1135)
This commit is contained in:
252
plugin/minecraftobserver/model.go
Normal file
252
plugin/minecraftobserver/model.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package minecraftobserver
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/google/uuid"
|
||||
"github.com/wdvxdr1123/ZeroBot/utils/helper"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ====================
|
||||
// DB Schema
|
||||
|
||||
// serverStatus 服务器状态
|
||||
type serverStatus struct {
|
||||
// ID 主键
|
||||
ID int64 `json:"id" gorm:"column:id;primary_key:pk_id;auto_increment;default:0"`
|
||||
// 服务器地址
|
||||
ServerAddr string `json:"server_addr" gorm:"column:server_addr;default:'';unique_index:udx_server_addr"`
|
||||
// 服务器描述
|
||||
Description string `json:"description" gorm:"column:description;default:null;type:CLOB"`
|
||||
// 在线玩家
|
||||
Players string `json:"players" gorm:"column:players;default:''"`
|
||||
// 版本
|
||||
Version string `json:"version" gorm:"column:version;default:''"`
|
||||
// FaviconMD5 Favicon MD5
|
||||
FaviconMD5 string `json:"favicon_md5" gorm:"column:favicon_md5;default:''"`
|
||||
// FaviconRaw 原始数据
|
||||
FaviconRaw icon `json:"favicon_raw" gorm:"column:favicon_raw;default:null;type:CLOB"`
|
||||
// 延迟,不可达时为-1
|
||||
PingDelay int64 `json:"ping_delay" gorm:"column:ping_delay;default:-1"`
|
||||
// 更新时间
|
||||
LastUpdate int64 `json:"last_update" gorm:"column:last_update;default:0"`
|
||||
}
|
||||
|
||||
// serverSubscribe 订阅信息
|
||||
type serverSubscribe struct {
|
||||
// ID 主键
|
||||
ID int64 `json:"id" gorm:"column:id;primary_key:pk_id;auto_increment;default:0"`
|
||||
// 服务器地址
|
||||
ServerAddr string `json:"server_addr" gorm:"column:server_addr;default:'';unique_index:udx_ait"`
|
||||
// 推送目标id
|
||||
TargetID int64 `json:"target_id" gorm:"column:target_id;default:0;unique_index:udx_ait"`
|
||||
// 类型 1:群组 2:个人
|
||||
TargetType int64 `json:"target_type" gorm:"column:target_type;default:0;unique_index:udx_ait"`
|
||||
// 更新时间
|
||||
LastUpdate int64 `json:"last_update" gorm:"column:last_update;default:0"`
|
||||
}
|
||||
|
||||
const (
|
||||
// pingDelayUnreachable 不可达
|
||||
pingDelayUnreachable = -1
|
||||
)
|
||||
|
||||
// isServerStatusSpecChanged 检查是否有状态变化
|
||||
func (ss *serverStatus) isServerStatusSpecChanged(newStatus *serverStatus) (res bool) {
|
||||
res = false
|
||||
if ss == nil || newStatus == nil {
|
||||
res = false
|
||||
return
|
||||
}
|
||||
// 描述变化、版本变化、Favicon变化
|
||||
if ss.Description != newStatus.Description || ss.Version != newStatus.Version || ss.FaviconMD5 != newStatus.FaviconMD5 {
|
||||
res = true
|
||||
return
|
||||
}
|
||||
// 状态由不可达变为可达 or 反之
|
||||
if (ss.PingDelay == pingDelayUnreachable && newStatus.PingDelay != pingDelayUnreachable) ||
|
||||
(ss.PingDelay != pingDelayUnreachable && newStatus.PingDelay == pingDelayUnreachable) {
|
||||
res = true
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// deepCopy 深拷贝
|
||||
func (ss *serverStatus) deepCopy() (dst *serverStatus) {
|
||||
if ss == nil {
|
||||
return
|
||||
}
|
||||
dst = &serverStatus{}
|
||||
*dst = *ss
|
||||
return
|
||||
}
|
||||
|
||||
// generateServerStatusMsg 生成服务器状态消息
|
||||
func (ss *serverStatus) generateServerStatusMsg() (msg string, iconBase64 string) {
|
||||
var msgBuilder strings.Builder
|
||||
if ss == nil {
|
||||
return
|
||||
}
|
||||
msgBuilder.WriteString(ss.Description)
|
||||
msgBuilder.WriteString("\n")
|
||||
msgBuilder.WriteString("服务器地址:")
|
||||
msgBuilder.WriteString(ss.ServerAddr)
|
||||
msgBuilder.WriteString("\n")
|
||||
// 版本
|
||||
msgBuilder.WriteString("版本:")
|
||||
msgBuilder.WriteString(ss.Version)
|
||||
msgBuilder.WriteString("\n")
|
||||
// Ping
|
||||
if ss.PingDelay < 0 {
|
||||
msgBuilder.WriteString("Ping延迟:超时\n")
|
||||
} else {
|
||||
msgBuilder.WriteString("Ping延迟:")
|
||||
msgBuilder.WriteString(fmt.Sprintf("%d 毫秒\n", ss.PingDelay))
|
||||
msgBuilder.WriteString("在线人数:")
|
||||
msgBuilder.WriteString(ss.Players)
|
||||
}
|
||||
// 图标
|
||||
if ss.FaviconRaw != "" && ss.FaviconRaw.checkPNG() {
|
||||
iconBase64 = ss.FaviconRaw.toBase64String()
|
||||
}
|
||||
msg = msgBuilder.String()
|
||||
return
|
||||
}
|
||||
|
||||
// DB Schema End
|
||||
|
||||
// ====================
|
||||
// Ping & List Response DTO
|
||||
|
||||
// serverPingAndListResp 服务器状态数据传输对象 From mc server response
|
||||
type serverPingAndListResp struct {
|
||||
Description chat.Message
|
||||
Players struct {
|
||||
Max int
|
||||
Online int
|
||||
Sample []struct {
|
||||
ID uuid.UUID
|
||||
Name string
|
||||
}
|
||||
}
|
||||
Version struct {
|
||||
Name string
|
||||
Protocol int
|
||||
}
|
||||
Favicon icon
|
||||
Delay time.Duration
|
||||
}
|
||||
|
||||
// icon should be a PNG image that is Base64 encoded
|
||||
// (without newlines: \n, new lines no longer work since 1.13)
|
||||
// and prepended with "data:image/png;base64,".
|
||||
type icon string
|
||||
|
||||
//func (i icon) toImage() (icon image.Image, err error) {
|
||||
// const prefix = "data:image/png;base64,"
|
||||
// if !strings.HasPrefix(string(i), prefix) {
|
||||
// return nil, errors.Errorf("server icon should prepended with %s", prefix)
|
||||
// }
|
||||
// base64png := strings.TrimPrefix(string(i), prefix)
|
||||
// r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64png))
|
||||
// icon, err = png.Decode(r)
|
||||
// return
|
||||
//}
|
||||
|
||||
// checkPNG 检查是否为PNG
|
||||
func (i icon) checkPNG() bool {
|
||||
const prefix = "data:image/png;base64,"
|
||||
return strings.HasPrefix(string(i), prefix)
|
||||
}
|
||||
|
||||
// toBase64String 转换为base64字符串
|
||||
func (i icon) toBase64String() string {
|
||||
return "base64://" + strings.TrimPrefix(string(i), "data:image/png;base64,")
|
||||
}
|
||||
|
||||
// genServerSubscribeSchema 将DTO转换为DB Schema
|
||||
func (dto *serverPingAndListResp) genServerSubscribeSchema(addr string, id int64) *serverStatus {
|
||||
if dto == nil {
|
||||
return nil
|
||||
}
|
||||
faviconMD5 := md5.Sum(helper.StringToBytes(string(dto.Favicon)))
|
||||
return &serverStatus{
|
||||
ID: id,
|
||||
ServerAddr: addr,
|
||||
Description: dto.Description.ClearString(),
|
||||
Version: dto.Version.Name,
|
||||
Players: fmt.Sprintf("%d/%d", dto.Players.Online, dto.Players.Max),
|
||||
FaviconMD5: hex.EncodeToString(faviconMD5[:]),
|
||||
FaviconRaw: dto.Favicon,
|
||||
PingDelay: dto.Delay.Milliseconds(),
|
||||
LastUpdate: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
// Ping & List Response DTO End
|
||||
// ====================
|
||||
|
||||
// ====================
|
||||
// Biz Model
|
||||
const (
|
||||
logPrefix = "[minecraft observer] "
|
||||
)
|
||||
|
||||
// warpTargetIDAndType 转换消息信息到订阅的目标ID和类型
|
||||
func warpTargetIDAndType(groupID, userID int64) (int64, int64) {
|
||||
// 订阅
|
||||
var targetID int64
|
||||
var targetType int64
|
||||
if groupID == 0 {
|
||||
targetType = targetTypeUser
|
||||
targetID = userID
|
||||
} else {
|
||||
targetType = targetTypeGroup
|
||||
targetID = groupID
|
||||
}
|
||||
return targetID, targetType
|
||||
}
|
||||
|
||||
// formatSubStatusChangeText 格式化状态变更文本
|
||||
func formatSubStatusChangeText(oldStatus, newStatus *serverStatus) string {
|
||||
var msgBuilder strings.Builder
|
||||
if oldStatus == nil || newStatus == nil {
|
||||
return ""
|
||||
}
|
||||
// 变更通知
|
||||
msgBuilder.WriteString("[Minecraft服务器状态变更通知]\n")
|
||||
// 地址
|
||||
msgBuilder.WriteString(fmt.Sprintf("服务器地址: %v\n", oldStatus.ServerAddr))
|
||||
// 描述
|
||||
if oldStatus.Description != newStatus.Description {
|
||||
msgBuilder.WriteString("\n-----[描述变更]-----\n")
|
||||
msgBuilder.WriteString(fmt.Sprintf("[旧]\n%v\n", oldStatus.Description))
|
||||
msgBuilder.WriteString(fmt.Sprintf("[新]\n%v\n", newStatus.Description))
|
||||
}
|
||||
// 版本
|
||||
if oldStatus.Version != newStatus.Version {
|
||||
msgBuilder.WriteString("\n-----[版本变更]-----\n")
|
||||
msgBuilder.WriteString(fmt.Sprintf("[旧]\n%v\n", oldStatus.Version))
|
||||
msgBuilder.WriteString(fmt.Sprintf("[新]\n%v\n", newStatus.Version))
|
||||
}
|
||||
// 状态由不可达变为可达,反之
|
||||
if oldStatus.PingDelay == pingDelayUnreachable && newStatus.PingDelay != pingDelayUnreachable {
|
||||
msgBuilder.WriteString("\n-----[Ping延迟]-----\n")
|
||||
msgBuilder.WriteString(fmt.Sprintf("[旧]\n超时\n"))
|
||||
msgBuilder.WriteString(fmt.Sprintf("[新]\n%v毫秒\n", newStatus.PingDelay))
|
||||
}
|
||||
if oldStatus.PingDelay != pingDelayUnreachable && newStatus.PingDelay == pingDelayUnreachable {
|
||||
msgBuilder.WriteString("\n-----[Ping延迟]-----\n")
|
||||
msgBuilder.WriteString(fmt.Sprintf("[旧]\n%v毫秒\n", oldStatus.PingDelay))
|
||||
msgBuilder.WriteString(fmt.Sprintf("[新]\n超时\n"))
|
||||
}
|
||||
return msgBuilder.String()
|
||||
}
|
||||
|
||||
// Biz Model End
|
||||
// ====================
|
||||
Reference in New Issue
Block a user