diff --git a/cmd.go b/cmd.go index 54e4b9e..647ba60 100644 --- a/cmd.go +++ b/cmd.go @@ -5,7 +5,9 @@ import ( "fmt" "log" "os" + "slices" "strconv" + "strings" "github.com/gtuk/discordwebhook" "github.com/multiplay/go-ts3" @@ -13,13 +15,15 @@ import ( // App holds the configuration type App struct { - discordURL string - discordUsername string - discordAvatarURL *string - tsQueryAddr string - tsQueryUser string - tsQueryPass string - tsQueryServerID int + discordURL string + discordUsername string + discordAvatarURL *string + tsQueryAddr string + tsQueryUser string + tsQueryPass string + tsQueryServerID int + spotLightGfxFormat string + spotLightIDMap map[string]int } func appFromEnv() (*App, error) { @@ -58,15 +62,33 @@ func appFromEnv() (*App, error) { return nil, errors.New("invalid TS_QUERY_SERVER_ID, must be int") } } + spotLightGfxFormat := os.Getenv("TS_SPOTLIGHT_GFX_FMT") + + // TODO: Load from environment variable + spotLightIDMap := make(map[string]int) + spotLightIDMap["rb+mT/4bh37gHzQYqTgBiEHG2IA="] = 0 + spotLightIDMap["sA3fHhvqmlSuFYtMoVYseRQI2DE="] = 0 + spotLightIDMap["9K6JV7kWaRU+4HFRkXrBZNjSmRA="] = 1 + spotLightIDMap["pFclzBx0w2UmwPd91VvaXJjYCYA="] = 2 + spotLightIDMap["tvjlpKqvcyQSCCVkT0TJ28uwdaQ="] = 3 + spotLightIDMap["SLLvtjVBmSoIzpMhlxnLa9CWoOU="] = 4 + spotLightIDMap["7EU/Up++D9+8SQk0sNchEuKPufw="] = 5 + spotLightIDMap["SyldxnLYWHJOUj3HnEsXGF6B0T4="] = 5 + spotLightIDMap["Mc/TdoNhddKdGtB55DSZrYk3NWc="] = 6 + spotLightIDMap["xOWMWG/TpkbV8XjahqqoQLsHHpA="] = 7 + spotLightIDMap["G4kg1LKJElM5LIpoeA6gN7DMl0c="] = 7 + spotLightIDMap["wuQ907NtzqL4uxhLk3P/TCpkXF0="] = 8 return &App{ - discordURL: discordURL, - discordUsername: discordUsername, - discordAvatarURL: discordAvatarURL, - tsQueryAddr: tsQueryAddr, - tsQueryUser: tsQueryUser, - tsQueryPass: tsQueryPass, - tsQueryServerID: tsQueryServerID, + discordURL: discordURL, + discordUsername: discordUsername, + discordAvatarURL: discordAvatarURL, + tsQueryAddr: tsQueryAddr, + tsQueryUser: tsQueryUser, + tsQueryPass: tsQueryPass, + tsQueryServerID: tsQueryServerID, + spotLightGfxFormat: spotLightGfxFormat, + spotLightIDMap: spotLightIDMap, }, nil } @@ -109,6 +131,8 @@ func main() { } clientMap := make(map[string]string) + clientDatabaseIDs := make(map[string]string) + clientUniqueIDs := make(map[string]string) log.Println("Online clients:") for _, client := range cl { @@ -117,8 +141,20 @@ func main() { } log.Println("-", client) clientMap[strconv.Itoa(client.ID)] = client.Nickname + clientDatabaseIDs[strconv.Itoa(client.ID)] = strconv.Itoa(client.DatabaseID) + + uid, err := getClientUniqueId(c, strconv.Itoa(client.DatabaseID)) + if err != nil { + log.Fatal(err) + } + + clientUniqueIDs[strconv.Itoa(client.ID)] = uid + log.Println(" - UID:", uid) } + // Update the banner on startup with the currently online users. + app.updateSpotLight(c, mapValues(clientUniqueIDs)) + // Listen for client updates notifs := c.Notifications() @@ -136,6 +172,12 @@ func main() { continue } + clientDBID, ok := event.Data["client_database_id"] + if !ok { + log.Println("User has no client database id", event.Data) + continue + } + clientNick, ok := event.Data["client_nickname"] if !ok { log.Println("User has no nickname:", clientID) @@ -147,6 +189,16 @@ func main() { if !previous { app.clientConnected(clientNick) + + clientDatabaseIDs[clientID] = clientDBID + uid, err := getClientUniqueId(c, clientDBID) + if err != nil { + log.Fatal(err) + } + + clientUniqueIDs[clientID] = uid + + app.updateSpotLight(c, mapValues(clientUniqueIDs)) } } else if event.Type == "clientleftview" { clientID, ok := event.Data["clid"] @@ -162,6 +214,9 @@ func main() { } delete(clientMap, clientID) + delete(clientDatabaseIDs, clientID) + delete(clientUniqueIDs, clientID) + app.updateSpotLight(c, mapValues(clientUniqueIDs)) app.clientDisconnected(clientNick) } } @@ -188,3 +243,63 @@ func (app *App) clientDisconnected(nick string) { content := fmt.Sprintf("Client disconnected: %s", nick) app.sendWebhook(content) } + +func (app *App) updateSpotLight(c *ts3.Client, connectedUIDs []string) { + if app.spotLightGfxFormat == "" { + return + } + + spotLightIDs := make([]int, 0) + for _, uid := range connectedUIDs { + spotLightID, ok := app.spotLightIDMap[uid] + if ok { + spotLightIDs = append(spotLightIDs, spotLightID) + } + } + + if len(spotLightIDs) == 0 { + updateBanner(c, fmt.Sprintf(app.spotLightGfxFormat, "empty")) + return + } + + slices.Sort(spotLightIDs) + slices.Compact(spotLightIDs) + + spotLightIDStrings := make([]string, len(spotLightIDs)) + for idx, id := range spotLightIDs { + spotLightIDStrings[idx] = strconv.Itoa(id) + } + joined := strings.Join(spotLightIDStrings, "_") + + updateBanner(c, fmt.Sprintf(app.spotLightGfxFormat, joined)) +} + +func updateBanner(c *ts3.Client, gfx string) { + err := c.Server.Edit(ts3.NewArg("virtualserver_hostbanner_gfx_url", gfx)) + if err != nil { + log.Println("Failed to update banner:", gfx, err) + } else { + log.Println("Updated banner:", gfx) + } +} + +func getClientUniqueId(c *ts3.Client, dbID string) (string, error) { + var uid = struct { + UID string `ms:"cluid"` + }{} + _, err := c.ExecCmd(ts3.NewCmd("clientgetnamefromdbid").WithArgs(ts3.NewArg("cldbid", dbID)).WithResponse(&uid)) + + if err != nil { + return "", err + } + + return uid.UID, nil +} + +func mapValues(m map[string]string) []string { + v := make([]string, 0, len(m)) + for _, val := range m { + v = append(v, val) + } + return v +}