summaryrefslogtreecommitdiff
path: root/api/minecraft/minecraft.go
blob: 16cc0f306cf8136dd8a4c7cb85b8fd1ba51d474a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package minecraft

import (
	"encoding/json"
	"io"
	"log"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"

	"penguins.lan/adapters/rcon"
	"penguins.lan/api/types"
	"penguins.lan/database"
)

const (
	queryTimeout = 3 * time.Second
	// staleAfter marks the cache offline if the scheduler stopped refreshing it.
	staleAfter = 30 * time.Second
)

type Status struct {
	Online  bool     `json:"online"`
	Count   int      `json:"count"`
	Max     int      `json:"max"`
	Players []string `json:"players"`
}

var listPattern = regexp.MustCompile(`There are (\d+) of a max of (\d+) players online:?\s*(.*)`)

// Query runs "list" over RCON. The scheduler calls this; requests read the cache.
func Query(address, password string) (Status, error) {
	client, err := rcon.Dial(address, password, queryTimeout)
	if err != nil {
		return Status{}, err
	}
	defer client.Close()

	out, err := client.Execute("list")
	if err != nil {
		return Status{}, err
	}
	return parseList(out), nil
}

func parseList(out string) Status {
	m := listPattern.FindStringSubmatch(strings.TrimSpace(out))
	if m == nil {
		return Status{Online: true}
	}

	count, _ := strconv.Atoi(m[1])
	max, _ := strconv.Atoi(m[2])

	players := []string{}
	for _, p := range strings.Split(m[3], ",") {
		if p = strings.TrimSpace(p); p != "" {
			players = append(players, p)
		}
	}
	return Status{Online: true, Count: count, Max: max, Players: players}
}

func StatusContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
	return func(success types.Continuation, _failure types.Continuation) types.ContinuationChain {
		resp.Header().Set("Content-Type", "application/json")

		cached, updatedAt, err := database.GetMinecraftStatus(context.DBConn)
		if err != nil {
			log.Println("minecraft: read cache failed:", err)
		}

		if cached == "" || time.Since(updatedAt) > staleAfter {
			json.NewEncoder(resp).Encode(Status{Online: false})
		} else {
			io.WriteString(resp, cached)
		}
		return success(context, req, resp)
	}
}