168 lines
3.5 KiB
Go
168 lines
3.5 KiB
Go
package models
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var timeLayout = "Mon Jan 2 15:04:05 2006"
|
|
|
|
type CoD4Server struct {
|
|
server string
|
|
port string
|
|
protocol string
|
|
timeout time.Duration
|
|
}
|
|
|
|
type CoD4ServerStatus struct {
|
|
raw string
|
|
serverData map[string]string
|
|
MapStartTime time.Time
|
|
MapName string
|
|
Score []Score
|
|
meta map[string]interface{}
|
|
}
|
|
|
|
func NewCOD4ServerStatus(server, port string, timeout time.Duration) (*CoD4Server, error) {
|
|
if server == "" || port == "" {
|
|
return nil, errors.New("server or port is empty")
|
|
}
|
|
|
|
return &CoD4Server{
|
|
server: server,
|
|
port: port,
|
|
protocol: "udp",
|
|
timeout: timeout,
|
|
}, nil
|
|
}
|
|
|
|
func (c *CoD4Server) GetServerStatus() (*CoD4ServerStatus, error) {
|
|
data, err := c.receiveData()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return parseServerData(data)
|
|
}
|
|
|
|
func (c *CoD4Server) receiveData() (string, error) {
|
|
address := fmt.Sprintf("%s:%s", c.server, c.port)
|
|
conn, err := net.DialTimeout(c.protocol, address, c.timeout)
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not connect to server: %s", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
conn.SetDeadline(time.Now().Add(c.timeout))
|
|
_, err = conn.Write([]byte("\xFF\xFF\xFF\xFFgetstatus\x00"))
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to send data to server: %s", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
response := make([]byte, 8192)
|
|
reader := bufio.NewReader(conn)
|
|
for {
|
|
n, err := reader.Read(response)
|
|
if err != nil {
|
|
break
|
|
}
|
|
buf.Write(response[:n])
|
|
}
|
|
|
|
data := buf.String()
|
|
if len(strings.TrimSpace(data)) == 0 {
|
|
return "", fmt.Errorf("no data received from server: %s", err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func parseServerData(data string) (*CoD4ServerStatus, error) {
|
|
lines := strings.Split(data, "\n")
|
|
|
|
if len(lines) < 2 {
|
|
return nil, fmt.Errorf("insufficient data received")
|
|
}
|
|
|
|
c := &CoD4ServerStatus{
|
|
raw: data,
|
|
serverData: make(map[string]string),
|
|
meta: make(map[string]interface{}),
|
|
}
|
|
|
|
tempPlayers := lines[2:]
|
|
tempData := strings.Split(lines[1], "\\")
|
|
|
|
for i := 1; i < len(tempData)-1; i += 2 {
|
|
c.serverData[tempData[i]] = tempData[i+1]
|
|
}
|
|
|
|
c.serverData["sv_hostname"] = colorCode(c.serverData["sv_hostname"])
|
|
c.serverData["_Maps"] = strings.Join(strings.Split(c.serverData["_Maps"], "-"), ",")
|
|
c.MapName = c.serverData["mapname"]
|
|
|
|
startTime, err := time.ParseInLocation(timeLayout, c.serverData["g_mapStartTime"], time.Local)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.MapStartTime = startTime
|
|
|
|
for _, playerLine := range tempPlayers {
|
|
if len(strings.TrimSpace(playerLine)) > 1 {
|
|
temp := strings.Fields(playerLine)
|
|
if len(temp) >= 3 {
|
|
playerName := strings.Trim(playerLine, `"`)
|
|
scoreValue, err := strconv.Atoi(temp[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pingValue, err := strconv.Atoi(temp[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.Score = append(c.Score, Score{
|
|
Name: playerName[strings.Index(playerName, "\"")+1:],
|
|
Score: scoreValue,
|
|
Ping: pingValue,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func colorCode(str string) string {
|
|
str += "^"
|
|
|
|
colorMap := map[string]string{
|
|
"0": "#000000",
|
|
"1": "#F65A5A",
|
|
"2": "#00F100",
|
|
"3": "#EFEE04",
|
|
"4": "#0F04E8",
|
|
"5": "#04E8E7",
|
|
"6": "#F75AF6",
|
|
"7": "#FFFFFF",
|
|
"8": "#7E7E7E",
|
|
"9": "#6E3C3C",
|
|
}
|
|
|
|
re := regexp.MustCompile(`\^(\d)(.*?)\^`)
|
|
return re.ReplaceAllStringFunc(str, func(m string) string {
|
|
matches := re.FindStringSubmatch(m)
|
|
return fmt.Sprintf(`<span style="color:%s;">%s</span>^`, colorMap[matches[1]], matches[2])
|
|
})
|
|
}
|