Skip to content

Instantly share code, notes, and snippets.

@cstewart90
Last active February 17, 2024 18:15
Show Gist options
  • Save cstewart90/b11974b1589fcce137f35573a632b0ea to your computer and use it in GitHub Desktop.
Save cstewart90/b11974b1589fcce137f35573a632b0ea to your computer and use it in GitHub Desktop.
package main
import (
"errors"
"net/http"
"strings"
"sync"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/rumblefrog/go-a2s"
)
const (
ServerTimeout = time.Second * 1
MinimumServerLength = 4
MaximumServers = 40
LimitPerSecond = 3
)
type serverInfo struct {
Server string `json:"server"`
Error string `json:"error"`
Name string `json:"name"`
Map string `json:"map"`
Folder string `json:"folder"`
Game string `json:"game"`
ID uint16 `json:"app_id"`
GameID uint64 `json:"game_id"`
Players uint8 `json:"players"`
MaxPlayers uint8 `json:"max_players"`
Bots uint8 `json:"bots"`
ServerType string `json:"server_type"`
ServerOS string `json:"server_os"`
Visibility bool `json:"password"`
VAC bool `json:"vac"`
Version string `json:"version"`
}
type infoResponse struct {
Error string `json:"error"`
Servers []serverInfo `json:"servers"`
}
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(LimitPerSecond)))
e.GET("/info", info)
e.Logger.Fatal(e.Start(":9090"))
}
func info(c echo.Context) error {
servers, err := getServers(c.QueryParam("servers"))
response := infoResponse{}
if err != nil {
response.Error = err.Error()
return c.JSON(http.StatusBadRequest, response)
}
var wg sync.WaitGroup
infos := make([]serverInfo, len(servers))
for i, server := range servers {
wg.Add(1)
go func(i int, server string) {
defer wg.Done()
infos[i] = getInfo(server)
}(i, server)
}
wg.Wait()
response.Servers = infos
return c.JSON(http.StatusOK, response)
}
func getServers(s string) ([]string, error) {
if len(s) == 0 {
return []string{}, errors.New("no servers provided")
}
s = strings.ReplaceAll(s, " ", "")
servers := strings.Split(s, ",")
servers = removeBlankServers(servers)
if len(servers) > MaximumServers {
return []string{}, errors.New("too many servers, max is 40")
}
return servers, nil
}
func removeBlankServers(servers []string) []string {
var newServers []string
for _, server := range servers {
if len(server) >= MinimumServerLength {
newServers = append(newServers, server)
}
}
return newServers
}
func getInfo(server string) serverInfo {
client, err := a2s.NewClient(server, a2s.TimeoutOption(ServerTimeout))
if err != nil {
return serverInfo{Server: server, Error: err.Error()}
}
defer client.Close()
info, err := client.QueryInfo()
if err != nil {
return serverInfo{Server: server, Error: err.Error()}
}
return serverInfo{
Server: server,
Error: "",
Name: info.Name,
Map: info.Map,
Folder: info.Folder,
Game: info.Game,
ID: info.ID,
GameID: info.ExtendedServerInfo.GameID,
Players: info.Players,
MaxPlayers: info.MaxPlayers,
Bots: info.Bots,
ServerType: info.ServerType.String(),
ServerOS: info.ServerOS.String(),
Visibility: info.Visibility,
VAC: info.VAC,
Version: info.Version,
}
}
module a2s-json
go 1.22
require (
github.com/labstack/echo/v4 v4.11.4
github.com/rumblefrog/go-a2s v1.0.2
)
require (
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment