forked from influxdata/telegraf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
minecraft.go
153 lines (132 loc) · 3.43 KB
/
minecraft.go
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package minecraft
import (
"fmt"
"regexp"
"strconv"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
const sampleConfig = `
## server address for minecraft
# server = "localhost"
## port for RCON
# port = "25575"
## password RCON for mincraft server
# password = ""
`
var (
playerNameRegex = regexp.MustCompile(`for\s([^:]+):-`)
scoreboardRegex = regexp.MustCompile(`(?U):\s(\d+)\s\((.*)\)`)
)
// Client is an interface for a client which gathers data from a minecraft server
type Client interface {
Gather(producer RCONClientProducer) ([]string, error)
}
// Minecraft represents a connection to a minecraft server
type Minecraft struct {
Server string
Port string
Password string
client Client
clientSet bool
}
// Description gives a brief description.
func (s *Minecraft) Description() string {
return "Collects scores from a minecraft server's scoreboard using the RCON protocol"
}
// SampleConfig returns our sampleConfig.
func (s *Minecraft) SampleConfig() string {
return sampleConfig
}
// Gather uses the RCON protocol to collect player and
// scoreboard stats from a minecraft server.
//var hasClient bool = false
func (s *Minecraft) Gather(acc telegraf.Accumulator) error {
// can't simply compare s.client to nil, because comparing an interface
// to nil often does not produce the desired result
if !s.clientSet {
var err error
s.client, err = NewRCON(s.Server, s.Port, s.Password)
if err != nil {
return err
}
s.clientSet = true
}
// (*RCON).Gather() takes an RCONClientProducer for testing purposes
d := defaultClientProducer{
Server: s.Server,
Port: s.Port,
}
scores, err := s.client.Gather(d)
if err != nil {
return err
}
for _, score := range scores {
player, err := ParsePlayerName(score)
if err != nil {
return err
}
tags := map[string]string{
"player": player,
"server": s.Server + ":" + s.Port,
}
stats, err := ParseScoreboard(score)
if err != nil {
return err
}
var fields = make(map[string]interface{}, len(stats))
for _, stat := range stats {
fields[stat.Name] = stat.Value
}
acc.AddFields("minecraft", fields, tags)
}
return nil
}
// ParsePlayerName takes an input string from rcon, to parse
// the player.
func ParsePlayerName(input string) (string, error) {
playerMatches := playerNameRegex.FindAllStringSubmatch(input, -1)
if playerMatches == nil {
return "", fmt.Errorf("no player was matched")
}
return playerMatches[0][1], nil
}
// Score is an individual tracked scoreboard stat.
type Score struct {
Name string
Value int
}
// ParseScoreboard takes an input string from rcon, to parse
// scoreboard stats.
func ParseScoreboard(input string) ([]Score, error) {
scoreMatches := scoreboardRegex.FindAllStringSubmatch(input, -1)
if scoreMatches == nil {
return nil, fmt.Errorf("No scores found")
}
var scores []Score
for _, match := range scoreMatches {
number := match[1]
name := match[2]
n, err := strconv.Atoi(number)
// Not necessary in current state, because regex can only match integers,
// maybe become necessary if regex is modified to match more types of
// numbers
if err != nil {
return nil, fmt.Errorf("Failed to parse score")
}
s := Score{
Name: name,
Value: n,
}
scores = append(scores, s)
}
return scores, nil
}
func init() {
inputs.Add("minecraft", func() telegraf.Input {
return &Minecraft{
Server: "localhost",
Port: "25575",
}
})
}