Initial commit
This commit is contained in:
89
models/capture.go
Normal file
89
models/capture.go
Normal 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
167
models/cod4server.go
Normal 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
4
models/persistence.go
Normal file
@ -0,0 +1,4 @@
|
||||
package models
|
||||
|
||||
type Persistence interface {
|
||||
}
|
||||
Reference in New Issue
Block a user