q3rcon/q3rcon.go

126 lines
2.5 KiB
Go
Raw Normal View History

2024-11-04 13:33:53 +00:00
package q3rcon
import (
"errors"
"strings"
"time"
"github.com/onyx-and-iris/q3rcon/internal/conn"
"github.com/onyx-and-iris/q3rcon/internal/packet"
)
// Option is a functional option type that allows us to configure the VbanTxt.
type Option func(*Rcon)
// WithLoginTimeout is a functional option to set the login timeout
func WithLoginTimeout(timeout time.Duration) Option {
return func(rcon *Rcon) {
rcon.loginTimeout = timeout
}
}
// WithDefaultTimeout is a functional option to set the default response timeout
2024-11-04 13:33:53 +00:00
func WithDefaultTimeout(timeout time.Duration) Option {
return func(rcon *Rcon) {
rcon.defaultTimeout = timeout
}
}
// WithTimeouts is a functional option to set the timeouts for responses per command
2024-11-04 13:33:53 +00:00
func WithTimeouts(timeouts map[string]time.Duration) Option {
return func(rcon *Rcon) {
rcon.timeouts = timeouts
}
}
type Rcon struct {
conn conn.UDPConn
request packet.Request
response packet.Response
loginTimeout time.Duration
2024-11-04 13:33:53 +00:00
defaultTimeout time.Duration
timeouts map[string]time.Duration
resp chan string
}
func New(host string, port int, password string, options ...Option) (*Rcon, error) {
if password == "" {
return nil, errors.New("no password provided")
}
conn, err := conn.New(host, port)
if err != nil {
return nil, err
}
r := &Rcon{
conn: conn,
request: packet.NewRequest(password),
resp: make(chan string),
loginTimeout: 5 * time.Second,
2024-11-04 13:33:53 +00:00
defaultTimeout: 20 * time.Millisecond,
timeouts: make(map[string]time.Duration),
}
for _, o := range options {
o(r)
}
if err = r.Login(); err != nil {
2024-11-04 13:33:53 +00:00
return nil, err
}
return r, nil
}
func (r Rcon) Login() error {
timeout := time.After(r.loginTimeout)
2024-11-04 13:33:53 +00:00
for {
select {
case <-timeout:
return errors.New("timeout logging in")
default:
resp, err := r.Send("login")
if err != nil {
return err
}
if resp == "" {
continue
}
if strings.Contains(resp, "Bad rcon") {
return errors.New("bad rcon password provided")
} else {
return nil
}
}
}
}
func (r Rcon) Send(cmd string) (string, error) {
timeout, ok := r.timeouts[cmd]
if !ok {
timeout = r.defaultTimeout
}
e := make(chan error)
go r.conn.Listen(timeout, r.resp, e)
2024-11-04 13:33:53 +00:00
_, err := r.conn.Write(r.request.Encode(cmd))
if err != nil {
return "", err
}
select {
case err := <-e:
return "", err
case resp := <-r.resp:
return strings.TrimPrefix(resp, string(r.response.Header())), nil
}
2024-11-04 13:33:53 +00:00
}
func (r Rcon) Close() {
r.conn.Close()
}