Initial commit

This commit is contained in:
Henri Burau
2024-06-03 18:07:27 +02:00
commit ca04cc51f3
43 changed files with 4440 additions and 0 deletions

89
models/capture.go Normal file
View File

@ -0,0 +1,89 @@
package models
import (
"cmp"
"slices"
"time"
"github.com/mergestat/timediff"
)
type MapScoreList []MapScore
type Capture struct {
Id uint
Host string
Port string
Name string
Active bool
Start time.Time
MapScores MapScoreList
}
type MapScore struct {
Id uint
StartTime time.Time
Map string
ScoreList []Score
}
type Score struct {
Id uint
Name string
Score int
Ping int
}
type ResultTable struct {
Header []ResultTableHeader
Rows []ResultTableRow
}
type ResultTableHeader struct {
Title string
Subtitle string
}
type ResultTableRow struct {
Name string
Total int
Individual []int
}
func (msl MapScoreList) BuildTable() *ResultTable {
rt := &ResultTable{
Header: []ResultTableHeader{{Title: "Name"}, {Title: "Total"}},
}
userMapRows := make(map[string]*ResultTableRow)
for mapIndex, mapScore := range msl {
rt.Header = append(rt.Header, ResultTableHeader{
Title: mapScore.Map,
Subtitle: timediff.TimeDiff(mapScore.StartTime),
})
for _, score := range mapScore.ScoreList {
if score.Ping > 0 {
if _, ok := userMapRows[score.Name]; !ok {
rt.Rows = append(rt.Rows, ResultTableRow{
Name: score.Name,
Total: 0,
Individual: make([]int, len(msl)),
})
userMapRows[score.Name] = &rt.Rows[len(rt.Rows)-1]
}
row := userMapRows[score.Name]
row.Total = row.Total + score.Score
row.Individual[mapIndex] = score.Score
}
}
}
slices.SortFunc(rt.Rows, func(a, b ResultTableRow) int {
return cmp.Compare(b.Total, a.Total)
})
return rt
}

167
models/cod4server.go Normal file
View File

@ -0,0 +1,167 @@
package models
import (
"bufio"
"bytes"
"errors"
"fmt"
"net"
"regexp"
"strconv"
"strings"
"time"
)
var timeLayout = "Mon Jun 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.Parse(timeLayout, c.serverData["g_mapStartTime"])
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])
})
}

4
models/persistence.go Normal file
View File

@ -0,0 +1,4 @@
package models
type Persistence interface {
}