使用Go获取Minecraft Motd


package utilitie

import (
   "bufio"
   "bytes"
   "encoding/binary"
   "encoding/json"
   "errors"
   "log"
   "net"
   "satellite/models/respon"
   "strconv"
   "strings"
   "time"
)

// java 1.7及以上版本motd结构
type JavaMotdJson17 struct {
   Version struct {
       Name              string `json:"name,omitempty"`
       Protocol          int    `json:"protocol,omitempty"`
       SupportedVersions []int  `json:"supportedVersions,omitempty"`
   } `json:"version"`
   Players struct {
       Max    int      `json:"max,omitempty"`
       Online int      `json:"online,omitempty"`
       Sample []Sample `json:"sample,omitempty"`
   } `json:"players"`
   Description DescriptionObjectOrString `json:"description,omitempty"`
   Modinfo     struct {
       Type    string `json:"type,omitempty"`
       ModList []struct {
           Modid   string `json:"modid,omitempty"`
           Version string `json:"version,omitempty"`
       } `json:"modList,omitempty"`
   } `json:"modinfo,omitempty"`
   Favicon             string `json:"favicon,omitempty"`
   EnforcesSecureChat  bool   `json:"enforcesSecureChat,omitempty,omitempty"`
   PreventsChatReports bool   `json:"preventsChatReports,omitempty"`
   Latency             int    `json:"latency,omitempty"`
}

type Sample struct {
   Name string `json:"name,omitempty"`
   Id   string `json:"id,omitempty"`
}

type DescriptionObjectOrString struct {
   DescriptionObject Description `json:"description,omitempty"`
   DescriptionString string
}
type Description struct {
   Text     string                `json:"text,omitempty"`
   ExtraObj []ExtraObjectOrString `json:"extra,omitempty"`
}
// 解析description属性
func (obj *DescriptionObjectOrString) UnmarshalJSON(data []byte) error {
   if err := json.Unmarshal(data, &obj.DescriptionString); err == nil {
       return nil
   }
   if err := json.Unmarshal(data, &obj.DescriptionObject); err == nil {
       return nil
   }
   return errors.New("invalid JSON: not a string or description object")
}

type ExtraObjectOrString struct {
   ExtraObject Extra `json:"extra,omitempty"`
   ExtraString string
}
type Extra struct {
   Text  string                `json:"text,omitempty"`
   Color string                `json:"color,omitempty"`
   Bold  bool                  `json:"bold,omitempty"`
   Extra []ExtraObjectOrString `json:"extra,omitempty"` // 递归
}

// 解析extra属性
func (obj *ExtraObjectOrString) UnmarshalJSON(data []byte) error {
   if err := json.Unmarshal(data, &obj.ExtraString); err == nil {
       return nil
   }
   if err := json.Unmarshal(data, &obj.ExtraObject); err == nil {
       return nil
   }
   return errors.New("invalid JSON: not a string or description object")
}

// 基岩版motd结构
type BedrockMotd struct {
   GameName        string `json:"gameName,omitempty"`
   HostName        string `json:"hostname,omitempty"`
   Protocol        string `json:"protocol,omitempty"`
   Version         string `json:"version,omitempty"`
   Players         int    `json:"players,omitempty"`
   MaxPlayers      int    `json:"max_players,omitempty"`
   ServerId        int64  `json:"serverId,omitempty"`
   Map             string `json:"map,omitempty"`
   GameMode        string `json:"game_mode,omitempty"`
   NintendoLimited bool   `json:"nintendo_limited,omitempty"`
   IPv4Port        int    `json:"ipv4port,omitempty"`
   IPv6Port        int    `json:"ipv6port,omitempty"`
   Extra           string `json:"extra,omitempty"`
   Latency         int    `json:"latency,omitempty"`
}
// java 1.4-1.6版本motd结构
type JavaMotd16 struct {
   Protocol      string `json:"protocol,omitempty"`
   Version       string `json:"version,omitempty"`
   MessageDaily  string `json:"message_daily,omitempty"`
   OnlinePlayers int    `json:"online_players,omitempty"`
   MaxPlayers    int    `json:"max_players,omitempty"`
   Latency       int    `json:"latency,omitempty"`
}

// java beta3-1.3版本motd结构
type JavaMotd13 struct {
   Motd          string `json:"motd,omitempty"`
   OnlinePlayers int    `json:"online_players,omitempty"`
   MaxPlayers    int    `json:"max_players,omitempty"`
   Latency       int    `json:"latency,omitempty"`
}




// OfflineMessageDataId 宏定义
var OfflineMessageDataId = []byte{0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78}
// Description 属性递归解析
func ExtraRawRecursion(extra []ExtraObjectOrString, descriptionRaw *[]respon.DescriptionRaw, depth int) {
   for _, v := range extra {
       result := respon.DescriptionRaw{}
       if v.ExtraString != "" {
           result.Text += v.ExtraString
       }
       if v.ExtraObject.Text != "" {
           result.Text += v.ExtraObject.Text
       }
       if v.ExtraObject.Color != "" {
           result.Color = v.ExtraObject.Color
       }
       if v.ExtraObject.Bold {
           result.Bold = v.ExtraObject.Bold
       }
       result.Depth = depth
       *descriptionRaw = append(*descriptionRaw, result)
       if v.ExtraObject.Extra != nil {
           ExtraRawRecursion(v.ExtraObject.Extra, descriptionRaw, depth+1)
       }
   }
}
// Description 属性递归解析
func ExtraRecursion(extra []ExtraObjectOrString) string {
   result := ""
   for _, v := range extra {
       if v.ExtraString != "" {
           result += v.ExtraString
       }
       if v.ExtraObject.Text != "" {
           result += v.ExtraObject.Text
       }
       if v.ExtraObject.Extra != nil {
           result += ExtraRecursion(v.ExtraObject.Extra)
       }
   }
   return result
}

// GetMotdFromJavaEdition17 (string, int) error
// 获取java版1.7及以上版本的motd
func GetMotdFromJavaEdition17(motd *JavaMotdJson17, host string, port int) error {

   buf := bytes.Buffer{}
   resultBuf := bytes.Buffer{}
   conn, err := net.DialTimeout("tcp", host+":"+strconv.Itoa(port), 1*time.Second)
   if err != nil {
       return err
   }
   defer conn.Close()
   // 写入包id
   buf.Write([]byte{0x00})
   // 写入请求协议版本
   buf.Write([]byte{0x01})
   // 写入host长度
   buf.Write([]byte{byte(len(host))})
   // 写入host
   buf.Write([]byte(host))
   // 写入port
   portBuf := make([]byte, 2)
   // short,大字节序列
   binary.BigEndian.PutUint16(portBuf, uint16(port))
   // 写入端口
   buf.Write(portBuf)
   // 写入状态
   buf.Write([]byte{0x01})
   // 写入包长度
   packageLength := []byte{uint8(buf.Len())}
   resultBuf.Write(append(packageLength, buf.Bytes()...))
   _, err = conn.Write(resultBuf.Bytes())
   if err != nil {
       return err
   }
   startTime := time.Now().UnixMilli()

   _, err = conn.Write([]byte{0x01, 0x00})
   if err != nil {
       return err
   }
   resBuf := bufio.NewReader(conn)
   err = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
   if err != nil {
       return err
   }

   // drop less data
   _, err = binary.ReadUvarint(resBuf)
   if err != nil {
       return err
   }
   endTime := time.Now().UnixMilli()
   motd.Latency = int(endTime - startTime)
   packetType, _ := resBuf.ReadByte()
   if bytes.Compare([]byte{packetType}, []byte{0x00}) != 0 {
       panic("error response packet type")
   }
   length, err := binary.ReadUvarint(resBuf)
   if length < 10 {
       return errors.New("error to small response")
   } else if length > 700000 {
       return errors.New("error to big response")
   }
   recBytes := make([]byte, length)
   // read buffer to recBytes
   for i := uint64(0); i < length; {
       n, _ := resBuf.Read(recBytes[i:length])
       i = i + uint64(n)
   }
   err = json.Unmarshal(recBytes, &motd)
   if err != nil {
       log.Fatalln(err.Error())
       return err
   }
   return nil
}

// GetMotdFromBedrockEdition (string, int) error
// 获取基岩版的motd
func GetMotdFromBedrockEdition(motd *BedrockMotd, host string, port int) error {
   originBuf := bytes.Buffer{}
   conn, err := net.DialTimeout("udp", host+":"+strconv.Itoa(port), 1*time.Second)
   if err != nil {
       return err
   }
   defer conn.Close()
   // 写入包id
   originBuf.Write([]byte{0x01})
   timeBuf := make([]byte, 8)
   binary.BigEndian.PutUint64(timeBuf, uint64(time.Now().Unix()))
   // 写入时间戳
   originBuf.Write(timeBuf)
   guidBuf := make([]byte, 8)
   // 写入 magic
   originBuf.Write(OfflineMessageDataId)
   binary.BigEndian.PutUint64(guidBuf, 2)
   // 写入guid
   originBuf.Write(guidBuf)
   _, err = conn.Write(originBuf.Bytes())
   if err != nil {
       return err
   }
   startTime := time.Now().UnixMilli()
   resBuf := bufio.NewReader(conn)
   err = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
   if err != nil {
       return err
   }
   firstByte, _ := resBuf.ReadByte()
   if bytes.Compare([]byte{firstByte}, []byte{0x1C}) != 0 {
       return errors.New("error response packet type")
   }
   endTime := time.Now().UnixMilli()
   motd.Latency = int(endTime - startTime)
   //丢 18 byte数据
   _, err = resBuf.Discard(16)
   if err != nil {
       return err
   }
   offlineMsg := make([]byte, 16)
   _, err = resBuf.Read(offlineMsg)
   if err != nil {
       return err
   }
   if bytes.Compare(offlineMsg, OfflineMessageDataId) != 0 {
       return errors.New("error response packet type")
   }
   length := make([]byte, 2)
   _, err = resBuf.Read(length)
   if err != nil {
       return err
   }
   lengthR := binary.BigEndian.Uint16(length)

   data := make([]byte, lengthR)

   for i := uint16(0); i < lengthR; {
       n, err := resBuf.Read(data[i:lengthR])
       if err != nil {
           return err
       }
       i = i + uint16(n)
   }
   for i, v := range strings.Split(string(data), ";") {
       switch i {
       case 0:
           motd.GameName = v
           continue
       case 1:
           motd.HostName = v
           continue
       case 2:
           motd.Protocol = v
           continue
       case 3:
           motd.Version = v
           continue
       case 4:
           motd.Players, err = strconv.Atoi(v)
           if err != nil {
               return err
           }
           continue
       case 5:
           motd.MaxPlayers, err = strconv.Atoi(v)
           if err != nil {
               return err
           }
           continue
       case 6:
           motd.ServerId, err = strconv.ParseInt(v, 10, 64)
           if err != nil {
               return err
           }
           continue
       case 7:
           motd.Map = v
           continue
       case 8:
           motd.GameMode = v
           continue
       case 9:
           motd.NintendoLimited, err = strconv.ParseBool(v)
           if err != nil {
               return err
           }
           continue
       case 10:
           motd.IPv4Port, err = strconv.Atoi(v)
           if err != nil {
               return err
           }
           continue
       case 11:
           motd.IPv6Port, err = strconv.Atoi(v)
           if err != nil {
               return err
           }
           continue
       case 12:
           motd.Extra = v
           continue
       }
   }
   return nil
}

// 获取java版1.5-1.6版本的motd gType 2为1.6版本 3为1.4版本
func GetMotdFromJavaEdition16(motd *JavaMotd16, host string, port int, gType int) error {

   conn, err := net.DialTimeout("tcp", host+":"+strconv.Itoa(port), 1*time.Second)
   if err != nil {
       return err
   }
   defer conn.Close()

   buf := bytes.Buffer{}
   if gType == 2 {
       // 写入包id
       buf.Write([]byte{0xFE, 0x01, 0xFA})
       // 写入包长度
       buf.Write([]byte{0x00, 0x0B})
       // 写入 MC|PingHost 字符串
       buf.Write([]byte{0x00, 0x4D, 0x00, 0x43, 0x00, 0x7C, 0x00, 0x50, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x67, 0x00, 0x48, 0x00, 0x6F, 0x00, 0x73, 0x00, 0x74})
       hostLen := make([]byte, 2)
       binary.BigEndian.PutUint16(hostLen, 7+uint16(len(host)))
       // 写入其余数据长度
       buf.Write(hostLen)
       // 写入协议版本
       buf.Write([]byte{0x4A})
       hostLen = make([]byte, 2)
       binary.BigEndian.PutUint16(hostLen, uint16(len(host)))
       // 写入host长度
       buf.Write(hostLen)
       // 写入host
       buf.Write([]byte(host))
       portByte := make([]byte, 4)
       binary.BigEndian.PutUint32(portByte, uint32(port))
       buf.Write(portByte)
   }

   if gType == 3 {
       buf.Write([]byte{0xFE, 0x01})
   }

   _, err = conn.Write(buf.Bytes())
   if err != nil {
       return err
   }
   startTime := time.Now().UnixMilli()

   resBuf := bufio.NewReader(conn)
   err = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
   firstByte, err := resBuf.ReadByte()
   if err != nil {
       return err
   }
   endTime := time.Now().UnixMilli()
   motd.Latency = int(endTime - startTime)

   if bytes.Compare([]byte{firstByte}, []byte{0xFF}) != 0 {
       return errors.New("error response packet type")
   }
   lengthByte := make([]byte, 2)
   _, err = resBuf.Read(lengthByte)
   length := 2 * binary.BigEndian.Uint16(lengthByte)

   data := make([]byte, length)
   for i := uint16(0); i < length; {
       n, err := resBuf.Read(data[i:length])
       if err != nil {
           return err
       }
       i = i + uint16(n)
   }
   data = bytes.ReplaceAll(data, []byte{0x00, 0xA7, 0x00, 0x31, 0x00, 0x00}, []byte{})

   for i, v := range bytes.Split(data, []byte{0x00, 0x00}) {
       byteData := bytes.ReplaceAll(v, []byte{0x00}, []byte{})
       switch i {
       case 0:
           motd.Protocol = string(byteData)
           if err != nil {
               return err
           }
           continue
       case 1:
           motd.Version = string(byteData)
           continue
       case 2:
           motd.MessageDaily = string(byteData)
           continue
       case 3:
           motd.OnlinePlayers, err = strconv.Atoi(string(byteData))
           if err != nil {
               return err
           }
           continue
       case 4:
           motd.MaxPlayers, err = strconv.Atoi(string(byteData))
           if err != nil {
               return err
           }
       }
   }
   return nil
}

// 获取java版beta3-1.3版本的motd
func GetMotdFromJavaVersion13(motd *JavaMotd13, host string, port int) error {
   conn, err := net.DialTimeout("tcp", host+":"+strconv.Itoa(port), 1*time.Second)
   if err != nil {
       return err
   }
   defer conn.Close()

   buf := bytes.Buffer{}
   // 写入包id
   buf.Write([]byte{0xFE})
   _, err = conn.Write(buf.Bytes())
   if err != nil {
       return err
   }
   startTime := time.Now().UnixMilli()

   resBuf := bufio.NewReader(conn)
   err = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
   firstByte, err := resBuf.ReadByte()
   if err != nil {
       return err
   }
   endTime := time.Now().UnixMilli()
   motd.Latency = int(endTime - startTime)

   if bytes.Compare([]byte{firstByte}, []byte{0xFF}) != 0 {
       return errors.New("error response packet type")
   }
   _, err = resBuf.Discard(4)
   if err != nil {
       return err
   }
   lengthByte := make([]byte, 2)
   _, err = resBuf.Read(lengthByte)
   if err != nil {
       return err
   }
   strLength := binary.BigEndian.Uint16(lengthByte)
   data := make([]byte, strLength+10)

   for i := uint16(0); i < strLength+10; {
       n, err := resBuf.Read(data[i : strLength+10])
       if err != nil {
           return err
       }
       i = i + uint16(n)
   }
   motd.Motd = string(bytes.ReplaceAll(data[:strLength], []byte{0x00}, []byte{}))
   for i, v := range bytes.Split(data[strLength+2:], []byte{0x00, 0xA7}) {
       byteData := bytes.ReplaceAll(v, []byte{0x00}, []byte{})
       if i == 0 {
           motd.OnlinePlayers, err = strconv.Atoi(string(byteData))
           if err != nil {
               return err
           }
           continue
       }
       if i == 1 {
           motd.MaxPlayers, err = strconv.Atoi(string(byteData))
           if err != nil {
               return err
           }
           continue
       }
   }
   return nil
}

路漫漫其修远兮,吾将上下而求索。
Built with Hugo
主题 StackJimmy 设计