diff --git a/README.md b/README.md index 49d56a6..dcaf039 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,12 @@ the interval (in seconds) between querying the same server. All servers are quer Then setup the servers you want to watch. `name` can be anything, the bot will use it for announcing, address should be in the `ip:port` form (where port is `the game port + 1`, i.e. if you see 27015 in the Steam server browser use 27016 here). `player_slots` is the number of -slots for players and `spec_slots` is spectator slots. The bot uses those to post "last minute" notifications. +slots for players and `spec_slots` is spectator slots. The bot uses those to post "last minute" notifications. `status_template` is an +optional parameter that defines the bot's status line. It's used to quickly see the server status without asking the bot directly. +The status is displayed on Discord as "Playing ...", you can specify the format in this parameter using Go's template syntax. See +`config_sample.json` for a full example with all available variables. tl;dr variables are used as `{{ .VarName }}`, all other characters +are printed as is. The variables are: `ServerName`, `Players`, `PlayerSlots`, `SpecSlots`, `FreeSlots`, `TotalSlots`, `Map`, `Skill`. +Hopefully, they're self-describing. The `seeding` section defines the player number boundaries. Inside that section there are two most important parameters, `seeding` (the bot will announce that the server is getting seeded when at least this many players have connected) and `almost_full` (it will say that the diff --git a/bot.go b/bot.go index d141af4..d6dc83b 100644 --- a/bot.go +++ b/bot.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto/tls" "fmt" "log" @@ -10,6 +11,7 @@ import ( "os/signal" "strings" "syscall" + "text/template" "time" "github.com/bwmarrin/discordgo" @@ -32,6 +34,17 @@ type message struct { channelID string } +type currentServerStatus struct { + ServerName string + Players int + TotalSlots int + PlayerSlots int + SpecSlots int + FreeSlots int + Map string + Skill int +} + var ( sendChan = make(chan message, 10) ) @@ -142,6 +155,49 @@ func handleCommand(s *discordgo.Session, m *discordgo.MessageCreate) { } } +func statusUpdate(restartChan chan struct{}, s *discordgo.Session) { + for { + status := &bytes.Buffer{} + for _, s := range config.Servers { + if s.statusTemplate != nil { + if status.Len() > 0 { + status.WriteString(" | ") + } + cs := currentServerStatus{ + ServerName: s.Name, + Players: len(s.players), + PlayerSlots: s.PlayerSlots, + SpecSlots: s.SpecSlots, + FreeSlots: s.SpecSlots + s.PlayerSlots - len(s.players), + TotalSlots: s.SpecSlots + s.PlayerSlots, + Map: s.currentMap, + Skill: s.avgSkill, + } + if err := s.statusTemplate.Execute(status, cs); err != nil { + log.Printf("Error executing template for server %s: %s", s.Name, err) + } + } + } + statusStr := "Natural Selection 2" + if status.Len() > 0 { + statusStr = status.String() + } + s.UpdateStatusComplex(discordgo.UpdateStatusData{ + Status: "online", + Activities: []*discordgo.Activity{{ + Type: discordgo.ActivityTypeGame, + Name: statusStr, + }}, + }) + select { + case <-time.After(config.QueryInterval * time.Second): + case <-restartChan: + log.Print("Restart request received, stopping status updater") + return + } + } +} + func sendMsg(c chan message, s *discordgo.Session) { for msg := range c { channelID := msg.channelID @@ -186,9 +242,17 @@ func bot() (err error) { defer dg.Close() dg.AddHandler(handleCommand) go sendMsg(sendChan, dg) - restartChan := make(chan bool) + restartChan := make(chan struct{}) for i := range config.Servers { config.Servers[i].restartChan = restartChan + if config.Servers[i].StatusTemplate != "" { + t, err := template.New(config.Servers[i].Address + "/template").Parse(config.Servers[i].StatusTemplate) + if err != nil { + log.Printf("Error in status template '%s': %s", config.Servers[i].StatusTemplate, err) + } else { + config.Servers[i].statusTemplate = t + } + } go query(config.Servers[i]) } for tid := range config.Threads { @@ -196,6 +260,7 @@ func bot() (err error) { log.Printf("Error joining thread %s: %s", tid, err) } } + go statusUpdate(restartChan, dg) fmt.Println("Bot is now running. Press CTRL-C to exit.") sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) diff --git a/config.go b/config.go index 07ab3d6..bcec1f9 100644 --- a/config.go +++ b/config.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "os" + "text/template" "time" ) @@ -20,8 +21,10 @@ const ( type ns2server struct { Name string Address string - SpecSlots int `json:"spec_slots"` - PlayerSlots int `json:"player_slots"` + SpecSlots int `json:"spec_slots"` + PlayerSlots int `json:"player_slots"` + StatusTemplate string `json:"status_template"` + statusTemplate *template.Template players []string serverState state maxStateToMessage state @@ -29,7 +32,7 @@ type ns2server struct { lastStatePromotion time.Time currentMap string avgSkill int - restartChan chan bool + restartChan chan struct{} failures int downSince *time.Time } diff --git a/config_sample.json b/config_sample.json index 9f05343..34f00df 100644 --- a/config_sample.json +++ b/config_sample.json @@ -15,7 +15,8 @@ "name": "Server 1", "address": "192.168.0.1:27016", "player_slots": 20, - "spec_slots": 6 + "spec_slots": 6, + "status_template": "{{ .ServerName }} Pl:{{ .Players }}/{{ .PlayerSlots }}+{{ .SpecSlots }}={{ .TotalSlots }};{{ .Map }}@{{ .Skill }}" }, { "name": "Server 2",