mirror of
https://github.com/MeowLynxSea/Uptimeow.git
synced 2025-07-09 10:54:38 +00:00
270 lines
6.2 KiB
Go
270 lines
6.2 KiB
Go
package rcon
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"github.com/MeowLynxSea/Uptimeow/config"
|
|
"github.com/robfig/cron/v3"
|
|
"log"
|
|
"net"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
type Connection struct {
|
|
conn net.Conn
|
|
pass string
|
|
addr string
|
|
}
|
|
|
|
const (
|
|
DataType_data_list = iota
|
|
DataType_data_tps
|
|
DataType_connection_error
|
|
DataType_connection_success
|
|
DataType_execution_error
|
|
)
|
|
|
|
var GlobalConfig config.ConfigData
|
|
var Cron = cron.New()
|
|
var isRunning bool
|
|
|
|
func InitRcon(callback func(data string)) {
|
|
|
|
GlobalConfig = config.Load()
|
|
|
|
var conn *Connection
|
|
|
|
Cron.AddFunc("@every 5s", func() {
|
|
command := [...]string{"list", "tps"}
|
|
for _, v := range command {
|
|
response, err := conn.SendCommand(v)
|
|
if err != nil {
|
|
// log.Println("[ERROR] Error executing command:", err)
|
|
callback("{\"type\": " + strconv.Itoa(DataType_execution_error) + ", \"data\": \"Error executing command: " + err.Error() + "\"}")
|
|
isRunning = false
|
|
break
|
|
} else {
|
|
switch v {
|
|
case "list":
|
|
// 编译正则表达式
|
|
onlinePlayerRegexp := regexp.MustCompile(`(\d+) of a max of (\d+) players online`)
|
|
playerListRegexp := regexp.MustCompile(`online: ([^:]+)`)
|
|
|
|
// 使用正则表达式提取信息
|
|
onlinePlayerMatches := onlinePlayerRegexp.FindStringSubmatch(response)
|
|
playerListMatches := playerListRegexp.FindStringSubmatch(response)
|
|
|
|
// 检查是否匹配成功
|
|
if onlinePlayerMatches != nil {
|
|
// 提取online_player和max_player
|
|
onlinePlayer, _ := strconv.Atoi(onlinePlayerMatches[1])
|
|
maxPlayer, _ := strconv.Atoi(onlinePlayerMatches[2])
|
|
|
|
var playerList string
|
|
if playerListMatches != nil {
|
|
playerList = playerListMatches[1]
|
|
}
|
|
|
|
// 创建一个结构体来保存数据
|
|
type PlayerData struct {
|
|
OnlinePlayer int `json:"online_player"`
|
|
MaxPlayer int `json:"max_player"`
|
|
PlayerList []string `json:"player_list"`
|
|
}
|
|
|
|
// 将玩家名字字符串分割成列表
|
|
playerNames := regexp.MustCompile(`, `).Split(playerList, -1)
|
|
|
|
// 实例化结构体并填充数据
|
|
data := PlayerData{
|
|
OnlinePlayer: onlinePlayer,
|
|
MaxPlayer: maxPlayer,
|
|
PlayerList: playerNames,
|
|
}
|
|
|
|
// 将结构体格式化为JSON
|
|
jsonData, err := json.MarshalIndent(data, "", " ")
|
|
if err != nil {
|
|
log.Println("[ERROR] Error marshalling JSON:", err)
|
|
}
|
|
|
|
callback("{\"type\": " + strconv.Itoa(DataType_data_list) + ", \"data\": " + string(jsonData) + "}")
|
|
} else {
|
|
log.Println("[ERROR] Could not extract the required information.")
|
|
isRunning = false
|
|
callback("{\"type\": " + strconv.Itoa(DataType_execution_error) + "}")
|
|
break
|
|
}
|
|
case "tps":
|
|
re := regexp.MustCompile(`§[a-zA-Z](\d+\.\d+|\d+)`)
|
|
matches := re.FindAllStringSubmatch(response, -1)
|
|
var numbers []string
|
|
if len(matches) != 3 {
|
|
isRunning = false
|
|
callback("{\"type\": " + strconv.Itoa(DataType_execution_error) + "}")
|
|
break
|
|
}
|
|
for _, match := range matches {
|
|
if len(match) > 1 {
|
|
numbers = append(numbers, match[1])
|
|
}
|
|
}
|
|
callback(`{
|
|
"type": ` + strconv.Itoa(DataType_data_tps) + `,
|
|
"data": {
|
|
"l1m": ` + numbers[0] + `,
|
|
"l5m": ` + numbers[1] + `,
|
|
"l15m": ` + numbers[2] + `
|
|
}
|
|
}`)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
for {
|
|
isRunning = true
|
|
log.Println("[INFO] Connecting to RCON server " + GlobalConfig.Rcon.Host + ":" + strconv.Itoa(GlobalConfig.Rcon.Port) + "...")
|
|
|
|
var err error
|
|
conn, err = NewConnection(GlobalConfig.Rcon.Host+":"+strconv.Itoa(GlobalConfig.Rcon.Port), GlobalConfig.Rcon.Password)
|
|
if err != nil {
|
|
callback("{\"type\": " + strconv.Itoa(DataType_connection_error) + ", \"data\": \"Error connecting to RCON server: " + err.Error() + "\"}")
|
|
isRunning = false
|
|
}
|
|
|
|
if isRunning {
|
|
callback("{\"type\": " + strconv.Itoa(DataType_connection_success) + "}")
|
|
Cron.Start()
|
|
}
|
|
|
|
for isRunning {
|
|
time.Sleep(10 * time.Nanosecond)
|
|
}
|
|
|
|
Cron.Stop()
|
|
|
|
log.Println("[INFO] RCON server has disconnected. Trying to reconnect in 1 seconds...")
|
|
time.Sleep(3 * time.Second)
|
|
}
|
|
}
|
|
|
|
var uniqueID int32 = 0
|
|
|
|
func NewConnection(addr, pass string) (*Connection, error) {
|
|
uniqueID++
|
|
conn, err := net.Dial("tcp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c := &Connection{conn: conn, pass: pass, addr: addr}
|
|
if err := c.auth(); err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func (c *Connection) SendCommand(cmd string) (string, error) {
|
|
err := c.sendCommand(2, []byte(cmd))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
pkg, err := c.readPkg()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(pkg.Body), err
|
|
}
|
|
|
|
func (c *Connection) auth() error {
|
|
c.sendCommand(3, []byte(c.pass))
|
|
pkg, err := c.readPkg()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if pkg.Type != 2 || pkg.ID != uniqueID {
|
|
return errors.New("incorrect password")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Connection) sendCommand(typ int32, body []byte) error {
|
|
size := int32(4 + 4 + len(body) + 2)
|
|
uniqueID += 1
|
|
id := uniqueID
|
|
|
|
wtr := binaryReadWriter{ByteOrder: binary.LittleEndian}
|
|
wtr.Write(size)
|
|
wtr.Write(id)
|
|
wtr.Write(typ)
|
|
wtr.Write(body)
|
|
wtr.Write([]byte{0x0, 0x0})
|
|
if wtr.err != nil {
|
|
return wtr.err
|
|
}
|
|
|
|
c.conn.Write(wtr.buf.Bytes())
|
|
return nil
|
|
}
|
|
|
|
func (c *Connection) readPkg() (pkg, error) {
|
|
const bufSize = 4096
|
|
b := make([]byte, bufSize)
|
|
|
|
// Doesn't handle split messages correctly.
|
|
read, err := c.conn.Read(b)
|
|
if err != nil {
|
|
return pkg{}, err
|
|
}
|
|
|
|
p := pkg{}
|
|
rdr := binaryReadWriter{ByteOrder: binary.LittleEndian,
|
|
buf: bytes.NewBuffer(b)}
|
|
rdr.Read(&p.Size)
|
|
rdr.Read(&p.ID)
|
|
rdr.Read(&p.Type)
|
|
body := [bufSize - 12]byte{}
|
|
rdr.Read(&body)
|
|
if rdr.err != nil {
|
|
return p, rdr.err
|
|
}
|
|
p.Body = body[:read-14]
|
|
return p, nil
|
|
}
|
|
|
|
type pkg struct {
|
|
Size int32
|
|
ID int32
|
|
Type int32
|
|
Body []byte
|
|
}
|
|
|
|
type binaryReadWriter struct {
|
|
ByteOrder binary.ByteOrder
|
|
err error
|
|
buf *bytes.Buffer
|
|
}
|
|
|
|
func (b *binaryReadWriter) Write(v interface{}) {
|
|
if b.err != nil {
|
|
return
|
|
}
|
|
if b.buf == nil {
|
|
b.buf = new(bytes.Buffer)
|
|
}
|
|
b.err = binary.Write(b.buf, b.ByteOrder, v)
|
|
}
|
|
|
|
func (b *binaryReadWriter) Read(v interface{}) {
|
|
if b.err != nil || b.buf == nil {
|
|
return
|
|
}
|
|
b.err = binary.Read(b.buf, b.ByteOrder, v)
|
|
}
|