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(`%s^`, colorMap[matches[1]], matches[2]) }) }