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)
}
}
|