From f9d54352e5b432d81b83b9f0174440ee27dfda17 Mon Sep 17 00:00:00 2001 From: Duncan Leo Date: Sun, 13 Oct 2019 19:43:32 +0800 Subject: [PATCH 1/5] update github.com/bwmarrin/discordgo --- go.mod | 7 ++++--- go.sum | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 83f6500..f08a6c6 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,15 @@ module github.com/bottleneckco/discord-radio require ( - github.com/bwmarrin/discordgo v0.18.0 + github.com/bwmarrin/discordgo v0.19.0 github.com/deckarep/golang-set v1.7.1 // indirect github.com/evalphobia/google-tts-go v0.3.0 - github.com/gorilla/websocket v1.4.0 // indirect + github.com/gorilla/websocket v1.4.1 // indirect github.com/joho/godotenv v1.3.0 github.com/masatana/go-textdistance v0.0.0-20171214133840-59954f2a9d67 github.com/stretchr/testify v1.3.0 // indirect - golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b // indirect + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect + golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect google.golang.org/api v0.1.0 ) diff --git a/go.sum b/go.sum index 81180b4..0aa3d23 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bwmarrin/discordgo v0.18.0 h1:XopVQXCIFy7Cr2eT7NcYcm4k0l2PYX+AP5RUbIWX2/8= github.com/bwmarrin/discordgo v0.18.0/go.mod h1:5NIvFv5Z7HddYuXbuQegZ684DleQaCFqChP2iuBivJ8= +github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5awQiY= +github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -18,6 +20,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -38,6 +42,11 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ= golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -46,12 +55,18 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3 h1:czFLhve3vsQetD6JOJ8NZZvGQIXlnN3/yXxbT6/awxI= golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= From 9cdee43d135646317b18bf0fc4aec148e6077aef Mon Sep 17 00:00:00 2001 From: Duncan Leo Date: Mon, 14 Oct 2019 22:54:09 +0800 Subject: [PATCH 2/5] add GuildSession struct + support multiple servers --- commands/commands.go | 51 ++++++++++++++++++++++----- commands/help.go | 3 +- commands/join.go | 9 +++-- commands/pause.go | 5 +-- commands/play.go | 19 +++++----- commands/player.go | 82 ++++++++++++++++++++------------------------ commands/queue.go | 13 +++---- commands/resume.go | 5 +-- commands/skip.go | 19 +++++----- commands/suicide.go | 5 +-- main.go | 37 +++++++++++--------- 11 files changed, 145 insertions(+), 103 deletions(-) diff --git a/commands/commands.go b/commands/commands.go index 5ee6978..c96624a 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -14,17 +14,52 @@ import ( youtube "google.golang.org/api/youtube/v3" ) +// CommandsMap a map of all the command handlers var CommandsMap = make(map[string]func(*discordgo.Session, *discordgo.MessageCreate)) -var Queue []models.QueueItem // current item = index 0 -var VoiceConnection *discordgo.VoiceConnection -var youtubeService *youtube.Service -var previousAutoPlaylistListing *youtube.PlaylistItem -var MusicPlayer = &Player{ - Close: make(chan struct{}), - Control: make(chan ControlMessage), + +// MusicPlayer represents a music player +type MusicPlayer struct { + StartTime time.Time + IsPlaying bool + Close chan struct{} + Control chan ControlMessage +} + +// GuildSession represents a guild voice session +type GuildSession struct { + Mutex sync.Mutex + Queue []models.QueueItem // current item = index 0 + VoiceConnection *discordgo.VoiceConnection + previousAutoPlaylistListing *youtube.PlaylistItem + MusicPlayer MusicPlayer +} + +func newGuildSession() GuildSession { + return GuildSession{ + Mutex: sync.Mutex{}, + MusicPlayer: MusicPlayer{ + Close: make(chan struct{}), + Control: make(chan ControlMessage), + }, + } } + +// GuildSessionMap a map of all the guild sessions +var GuildSessionMap = make(map[string]*GuildSession) + +func safeGetGuildSession(guildID string) *GuildSession { + if session, ok := GuildSessionMap[guildID]; ok { + return session + } + session := newGuildSession() + GuildSessionMap[guildID] = &session + return &session +} + +// GameUpdateFunc call to update the bot's current game var GameUpdateFunc func(game string) -var Mutex = sync.Mutex{} + +var youtubeService *youtube.Service func init() { godotenv.Load() diff --git a/commands/help.go b/commands/help.go index 7e54369..b48a3fa 100644 --- a/commands/help.go +++ b/commands/help.go @@ -9,10 +9,11 @@ import ( ) func help(s *discordgo.Session, m *discordgo.MessageCreate) { + guildSession := safeGetGuildSession(m.GuildID) var b strings.Builder for k := range CommandsMap { b.WriteString(fmt.Sprintf("%s%s\n", os.Getenv("BOT_COMMAND_PREFIX"), k)) } s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s commands list:\n%s", m.Author.Mention(), b.String())) - SafeCheckPlay() + SafeCheckPlay(guildSession) } diff --git a/commands/join.go b/commands/join.go index cb748d4..2ca2f85 100644 --- a/commands/join.go +++ b/commands/join.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "log" "os" "os/exec" @@ -11,6 +12,7 @@ import ( ) func join(s *discordgo.Session, m *discordgo.MessageCreate) { + guildSession := safeGetGuildSession(m.GuildID) voiceState, err := util.FindUserVoiceState(s, m.Author.ID) if err != nil { s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s you are not in a voice channel", m.Author.Mention())) @@ -26,8 +28,9 @@ func join(s *discordgo.Session, m *discordgo.MessageCreate) { s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s error occurred: %s", m.Author.Mention(), err)) return } - VoiceConnection = voiceChannel + guildSession.VoiceConnection = voiceChannel s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s joined '%s'", m.Author.Mention(), channel.Name)) + log.Printf(fmt.Sprintf("%s joined '%s' guild '%s'\n", m.Author.Mention(), channel.Name, m.GuildID)) url, _ := googletts.GetTTSURL("Ready", "en") if os.Getenv("BOT_UPDATE_YTDL") == "true" { updateCmd := exec.Command("/usr/bin/curl", "-L", "https://yt-dl.org/downloads/latest/youtube-dl", "-o", "/usr/local/bin/youtube-dl") @@ -35,6 +38,6 @@ func join(s *discordgo.Session, m *discordgo.MessageCreate) { updateCmd.Stderr = os.Stderr updateCmd.Run() } - MusicPlayer.Play(url, "0.5") - SafeCheckPlay() + guildSession.Play(url, "0.5") + SafeCheckPlay(guildSession) } diff --git a/commands/pause.go b/commands/pause.go index d391ffc..fd4e8d6 100644 --- a/commands/pause.go +++ b/commands/pause.go @@ -7,10 +7,11 @@ import ( ) func pause(s *discordgo.Session, m *discordgo.MessageCreate) { - if !MusicPlayer.IsPlaying { + guildSession := safeGetGuildSession(m.GuildID) + if !guildSession.MusicPlayer.IsPlaying { s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s nothing to pause", m.Author.Mention())) return } - MusicPlayer.Control <- Pause + guildSession.MusicPlayer.Control <- Pause s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s paused", m.Author.Mention())) } diff --git a/commands/play.go b/commands/play.go index 05b55ab..20ee7a5 100644 --- a/commands/play.go +++ b/commands/play.go @@ -18,7 +18,8 @@ import ( var tempSearchResultsCache = make(map[string][]*youtube.SearchResult) func play(s *discordgo.Session, m *discordgo.MessageCreate) { - if VoiceConnection == nil { + guildSession := safeGetGuildSession(m.GuildID) + if guildSession.VoiceConnection == nil { s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s I am not in any voice channel", m.Author.Mention())) return } @@ -62,9 +63,9 @@ func play(s *discordgo.Session, m *discordgo.MessageCreate) { s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Error occurred: %s", err)) return } - Mutex.Lock() + guildSession.Mutex.Lock() for _, youtubeListing := range youtubeListings.Items { - Queue = append(Queue, models.QueueItem{ + guildSession.Queue = append(guildSession.Queue, models.QueueItem{ Title: youtubeListing.Snippet.Title, ChannelTitle: youtubeListing.Snippet.ChannelTitle, Author: m.Author.Username, @@ -72,8 +73,8 @@ func play(s *discordgo.Session, m *discordgo.MessageCreate) { Thumbnail: youtubeListing.Snippet.Thumbnails.Default.Url, }) } - Mutex.Unlock() - SafeCheckPlay() + guildSession.Mutex.Unlock() + SafeCheckPlay(guildSession) s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s enqueued %d videos`\n", m.Author.Mention(), len(videoIDs))) } else { maxResults, _ := strconv.ParseInt(os.Getenv("BOT_NUM_RESULTS"), 10, 64) @@ -112,15 +113,15 @@ func play(s *discordgo.Session, m *discordgo.MessageCreate) { return } else if err == nil { chosenItem := tempSearchResultsCache[mm.Author.ID][choice-1] - Mutex.Lock() - Queue = append(Queue, models.QueueItem{ + guildSession.Mutex.Lock() + guildSession.Queue = append(guildSession.Queue, models.QueueItem{ Title: chosenItem.Snippet.Title, ChannelTitle: chosenItem.Snippet.ChannelTitle, Author: mm.Author.Username, VideoID: chosenItem.Id.VideoId, Thumbnail: chosenItem.Snippet.Thumbnails.Default.Url, }) - Mutex.Unlock() + guildSession.Mutex.Unlock() ss.ChannelMessageSendEmbed(mm.ChannelID, &discordgo.MessageEmbed{ Author: &discordgo.MessageEmbedAuthor{ Name: "Added to queue", @@ -143,7 +144,7 @@ func play(s *discordgo.Session, m *discordgo.MessageCreate) { } delete(tempSearchResultsCache, mm.Author.ID) awaitFuncRemove() - SafeCheckPlay() + SafeCheckPlay(guildSession) }) } diff --git a/commands/player.go b/commands/player.go index c010ffc..034fafe 100644 --- a/commands/player.go +++ b/commands/player.go @@ -30,23 +30,16 @@ const ( Resume ) -type Player struct { - StartTime time.Time - IsPlaying bool - Close chan struct{} - Control chan ControlMessage -} - // Huge thanks to https://github.com/iopred/bruxism/blob/master/musicplugin/musicplugin.go -func (p *Player) Play(url, volume string) { +func (guildSession *GuildSession) Play(url, volume string) { log.Printf("[PLAYER] Playing URL '%s'\n", url) log.Println("[PLAYER] IsPlaying=true") - p.IsPlaying = true + guildSession.MusicPlayer.IsPlaying = true defer func() { log.Println("[PLAYER] IsPlaying=false") - p.IsPlaying = false + guildSession.MusicPlayer.IsPlaying = false }() args := []string{"-q"} @@ -113,29 +106,29 @@ func (p *Player) Play(url, volume string) { var opuslen int16 // Send "speaking" packet over the voice websocket - if VoiceConnection != nil { - VoiceConnection.Speaking(true) + if guildSession.VoiceConnection != nil { + guildSession.VoiceConnection.Speaking(true) } // Send not "speaking" packet over the websocket when we finish defer func() { - if VoiceConnection != nil { - VoiceConnection.Speaking(false) + if guildSession.VoiceConnection != nil { + guildSession.VoiceConnection.Speaking(false) } }() - p.StartTime = time.Now() + guildSession.MusicPlayer.StartTime = time.Now() for { select { - case <-p.Close: + case <-guildSession.MusicPlayer.Close: log.Println("play() exited due to close channel.") return default: } select { - case ctl := <-p.Control: + case ctl := <-guildSession.MusicPlayer.Control: switch ctl { case Skip: log.Println("received skip") @@ -144,7 +137,7 @@ func (p *Player) Play(url, volume string) { done := false for { - ctl, ok := <-p.Control + ctl, ok := <-guildSession.MusicPlayer.Control if !ok { return } @@ -187,8 +180,8 @@ func (p *Player) Play(url, volume string) { } // Send received PCM to the sendPCM channel - if VoiceConnection != nil { - VoiceConnection.OpusSend <- opus + if guildSession.VoiceConnection != nil { + guildSession.VoiceConnection.OpusSend <- opus } else { log.Println("[PLAYER] VoiceConnection nil, terminating OPUS transmission") return @@ -196,7 +189,7 @@ func (p *Player) Play(url, volume string) { } } -func GenerateAutoPlaylistQueueItem() (models.QueueItem, error) { +func GenerateAutoPlaylistQueueItem(guildSession *GuildSession) (models.QueueItem, error) { var data models.QueueItem parsedURI, err := url.ParseRequestURI(os.Getenv("BOT_AUTO_PLAYLIST")) if err != nil { @@ -234,9 +227,9 @@ func GenerateAutoPlaylistQueueItem() (models.QueueItem, error) { } chosenListingSnippet = chosenListingSnippets.Items[0] - if previousAutoPlaylistListing != nil && textdistance.LevenshteinDistance(previousAutoPlaylistListing.Snippet.Title, chosenListingSnippet.Snippet.Title) > 20 { - previousAutoPlaylistListing = chosenListing - previousAutoPlaylistListing.Snippet = &youtube.PlaylistItemSnippet{Title: chosenListingSnippet.Snippet.Title} + if guildSession.previousAutoPlaylistListing != nil && textdistance.LevenshteinDistance(guildSession.previousAutoPlaylistListing.Snippet.Title, chosenListingSnippet.Snippet.Title) > 20 { + guildSession.previousAutoPlaylistListing = chosenListing + guildSession.previousAutoPlaylistListing.Snippet = &youtube.PlaylistItemSnippet{Title: chosenListingSnippet.Snippet.Title} break } else { break @@ -255,48 +248,49 @@ func GenerateAutoPlaylistQueueItem() (models.QueueItem, error) { return data, nil } -func SafeCheckPlay() { - if VoiceConnection == nil { +func SafeCheckPlay(guildSession *GuildSession) { + if guildSession.VoiceConnection == nil { log.Println("[SCP] no voice connection") return } - if MusicPlayer.IsPlaying { + if guildSession.MusicPlayer.IsPlaying { log.Println("[SCP] currently playing something!") return } - if len(Queue) == 0 && len(os.Getenv("BOT_AUTO_PLAYLIST")) == 0 { + if len(guildSession.Queue) == 0 && len(os.Getenv("BOT_AUTO_PLAYLIST")) == 0 { log.Println("[SCP] no items in queue") return - } else if len(Queue) == 0 { + } else if len(guildSession.Queue) == 0 { log.Println("[SCP] Getting from auto playlist") - queueItem, err := GenerateAutoPlaylistQueueItem() + queueItem, err := GenerateAutoPlaylistQueueItem(guildSession) if err != nil { log.Printf("[SCP] Error generating auto playlist item: %s\n", err) return } - Mutex.Lock() - Queue = append(Queue, queueItem) - Mutex.Unlock() + guildSession.Mutex.Lock() + guildSession.Queue = append(guildSession.Queue, queueItem) + log.Printf("SCP %+v\n", GuildSessionMap) + guildSession.Mutex.Unlock() } - Mutex.Lock() - var song = Queue[0] - Mutex.Unlock() + guildSession.Mutex.Lock() + var song = guildSession.Queue[0] + guildSession.Mutex.Unlock() GameUpdateFunc("with myself") if ttsMsgURL, err := googletts.GetTTSURL(fmt.Sprintf("Music: %s", sanitiseSongTitle(song.Title)), "en"); err == nil { log.Println("[PLAYER] Announcing upcoming song title") - MusicPlayer.Play(ttsMsgURL, "0.5") + guildSession.Play(ttsMsgURL, "0.5") } GameUpdateFunc(fmt.Sprintf("%s (%s)", song.Title, song.ChannelTitle)) log.Println("[PLAYER] Playing the actual song data") - MusicPlayer.Play(fmt.Sprintf("https://www.youtube.com/watch?v=%s", song.VideoID), os.Getenv("BOT_VOLUME")) - Mutex.Lock() - if len(Queue) > 0 { - Queue = Queue[1:] + guildSession.Play(fmt.Sprintf("https://www.youtube.com/watch?v=%s", song.VideoID), os.Getenv("BOT_VOLUME")) + guildSession.Mutex.Lock() + if len(guildSession.Queue) > 0 { + guildSession.Queue = guildSession.Queue[1:] } - Mutex.Unlock() - if VoiceConnection != nil { - go SafeCheckPlay() + guildSession.Mutex.Unlock() + if guildSession.VoiceConnection != nil { + go SafeCheckPlay(guildSession) } } diff --git a/commands/queue.go b/commands/queue.go index 91142c5..663b728 100644 --- a/commands/queue.go +++ b/commands/queue.go @@ -9,20 +9,21 @@ import ( ) func queue(s *discordgo.Session, m *discordgo.MessageCreate) { - Mutex.Lock() - if len(Queue) == 0 { + guildSession := safeGetGuildSession(m.GuildID) + guildSession.Mutex.Lock() + if len(guildSession.Queue) == 0 { s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s nothing in the queue.", m.Author.Mention())) - Mutex.Unlock() + guildSession.Mutex.Unlock() return } var b strings.Builder b.WriteString(fmt.Sprintf("%s here is the queue:\n", m.Author.Mention())) - b.WriteString(fmt.Sprintf("⏯ **%s** ▶️️%s ⏫%s\n", Queue[0].Title, Queue[0].ChannelTitle, Queue[0].Author)) - for index, queueItem := range Queue[1:] { + b.WriteString(fmt.Sprintf("⏯ **%s** ▶️️%s ⏫%s\n", guildSession.Queue[0].Title, guildSession.Queue[0].ChannelTitle, guildSession.Queue[0].Author)) + for index, queueItem := range guildSession.Queue[1:] { b.WriteString(fmt.Sprintf("`️%d.` **%s** ⬆️%s ⏫%s\n", index+2, queueItem.Title, queueItem.ChannelTitle, queueItem.Author)) } - Mutex.Unlock() + guildSession.Mutex.Unlock() log.Println("Off") s.ChannelMessageSend(m.ChannelID, b.String()) } diff --git a/commands/resume.go b/commands/resume.go index 14eeb5e..9a475ea 100644 --- a/commands/resume.go +++ b/commands/resume.go @@ -7,10 +7,11 @@ import ( ) func resume(s *discordgo.Session, m *discordgo.MessageCreate) { - if !MusicPlayer.IsPlaying { + guildSession := safeGetGuildSession(m.GuildID) + if !guildSession.MusicPlayer.IsPlaying { s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s nothing to resume", m.Author.Mention())) return } - MusicPlayer.Control <- Resume + guildSession.MusicPlayer.Control <- Resume s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s resumed", m.Author.Mention())) } diff --git a/commands/skip.go b/commands/skip.go index fb205f4..67d53c2 100644 --- a/commands/skip.go +++ b/commands/skip.go @@ -10,29 +10,30 @@ import ( ) func skip(s *discordgo.Session, m *discordgo.MessageCreate) { - Mutex.Lock() - if len(Queue) == 0 || !MusicPlayer.IsPlaying { + guildSession := safeGetGuildSession(m.GuildID) + guildSession.Mutex.Lock() + if len(guildSession.Queue) == 0 || !guildSession.MusicPlayer.IsPlaying { s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s nothing to skip", m.Author.Mention())) return } var skippedItem models.QueueItem if len(m.Content) == 0 { // No args, skip current - skippedItem = Queue[0] + skippedItem = guildSession.Queue[0] // Queue = append(Queue[:0], Queue[1:]...) - MusicPlayer.Control <- Skip + guildSession.MusicPlayer.Control <- Skip } else { choice, err := strconv.ParseInt(m.Content, 10, 64) - if err == nil && (choice-1 >= 0 && choice-1 < int64(len(Queue))) { - skippedItem = Queue[choice-1] - Queue = append(Queue[:choice-1], Queue[choice:]...) + if err == nil && (choice-1 >= 0 && choice-1 < int64(len(guildSession.Queue))) { + skippedItem = guildSession.Queue[choice-1] + guildSession.Queue = append(guildSession.Queue[:choice-1], guildSession.Queue[choice:]...) } else { s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s invalid choice", m.Author.Mention())) - Mutex.Unlock() + guildSession.Mutex.Unlock() return } } - Mutex.Unlock() + guildSession.Mutex.Unlock() s.ChannelMessageSendEmbed(m.ChannelID, &discordgo.MessageEmbed{ Author: &discordgo.MessageEmbedAuthor{ Name: "Removed from queue", diff --git a/commands/suicide.go b/commands/suicide.go index 620e83a..d30a1c3 100644 --- a/commands/suicide.go +++ b/commands/suicide.go @@ -7,9 +7,10 @@ import ( ) func suicide(s *discordgo.Session, m *discordgo.MessageCreate) { + guildSession := safeGetGuildSession(m.GuildID) s.ChannelMessageSend(m.ChannelID, "Goodbye, cruel world!") - if VoiceConnection != nil { - VoiceConnection.Disconnect() + if guildSession.VoiceConnection != nil { + guildSession.VoiceConnection.Disconnect() } os.Exit(1) } diff --git a/main.go b/main.go index 174c0ab..c2bd3e5 100644 --- a/main.go +++ b/main.go @@ -41,15 +41,16 @@ func main() { }) dg.AddHandler(func(s *discordgo.Session, vsu *discordgo.VoiceStateUpdate) { - if commands.VoiceConnection == nil { + guildSession, ok := commands.GuildSessionMap[vsu.GuildID] + if !ok || guildSession.VoiceConnection == nil { return } - channel, err := s.Channel(commands.VoiceConnection.ChannelID) + channel, err := s.Channel(guildSession.VoiceConnection.ChannelID) if err != nil { log.Println(err) return } - if channel.ID != commands.VoiceConnection.ChannelID { + if channel.ID != guildSession.VoiceConnection.ChannelID { // Not my voice channel return } @@ -69,29 +70,29 @@ func main() { } if len(ttsMsg) > 0 { url, _ := googletts.GetTTSURL(ttsMsg, "en") - var isSomethingPlaying = commands.MusicPlayer.IsPlaying + var isSomethingPlaying = guildSession.MusicPlayer.IsPlaying if isSomethingPlaying { - commands.MusicPlayer.Control <- commands.Pause + guildSession.MusicPlayer.Control <- commands.Pause } - commands.MusicPlayer.Play(url, "0.5") + guildSession.Play(url, "0.5") if isSomethingPlaying { - commands.MusicPlayer.Control <- commands.Resume + guildSession.MusicPlayer.Control <- commands.Resume log.Println("[MAIN] Patching MusicPlayer IsPlaying=true") - commands.MusicPlayer.IsPlaying = true + guildSession.MusicPlayer.IsPlaying = true } } - if len(util.GetUsersInVoiceChannel(s, commands.VoiceConnection.ChannelID)) == 1 { + if len(util.GetUsersInVoiceChannel(s, guildSession.VoiceConnection.ChannelID)) == 1 { // Only bot left log.Println("Leaving, only me left in voice channel.") s.UpdateStatus(1, "") - var tempVoiceConn = commands.VoiceConnection - commands.VoiceConnection = nil + var tempVoiceConn = guildSession.VoiceConnection + guildSession.VoiceConnection = nil - commands.Mutex.Lock() - commands.Queue = commands.Queue[0:0] - commands.Mutex.Unlock() - commands.MusicPlayer.Close <- struct{}{} + guildSession.Mutex.Lock() + guildSession.Queue = guildSession.Queue[0:0] + guildSession.Mutex.Unlock() + guildSession.MusicPlayer.Close <- struct{}{} tempVoiceConn.Disconnect() } }) @@ -111,8 +112,10 @@ func main() { signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) <-sc - if commands.VoiceConnection != nil { - commands.VoiceConnection.Disconnect() + for _, guildSession := range commands.GuildSessionMap { + if guildSession.VoiceConnection != nil { + guildSession.VoiceConnection.Disconnect() + } } // Cleanly close down the Discord session. From b6dd59b408375b23dc029ef08e7e91543b6eb5bf Mon Sep 17 00:00:00 2001 From: Duncan Leo Date: Mon, 14 Oct 2019 23:03:56 +0800 Subject: [PATCH 3/5] add leave command --- commands/commands.go | 1 + commands/leave.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 commands/leave.go diff --git a/commands/commands.go b/commands/commands.go index c96624a..12d6806 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -83,6 +83,7 @@ func init() { CommandsMap["pause"] = pause CommandsMap["resume"] = resume CommandsMap["help"] = help + CommandsMap["leave"] = leave } func deleteMessageDelayed(sess *discordgo.Session, msg *discordgo.Message) { diff --git a/commands/leave.go b/commands/leave.go new file mode 100644 index 0000000..ac13307 --- /dev/null +++ b/commands/leave.go @@ -0,0 +1,37 @@ +package commands + +import ( + "fmt" + + "github.com/bottleneckco/discord-radio/util" + "github.com/bwmarrin/discordgo" +) + +func leave(s *discordgo.Session, m *discordgo.MessageCreate) { + if guildSession, ok := GuildSessionMap[m.GuildID]; ok { + voiceState, err := util.FindUserVoiceState(s, m.Author.ID) + if err != nil { + s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s you are not in a voice channel", m.Author.Mention())) + return + } + channel, err := s.Channel(voiceState.ChannelID) + if err != nil { + s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s error occurred: %s", m.Author.Mention(), err)) + return + } + // Actual disconnect code + var tempVoiceConn = guildSession.VoiceConnection + guildSession.VoiceConnection = nil + + guildSession.Mutex.Lock() + guildSession.Queue = guildSession.Queue[0:0] + guildSession.Mutex.Unlock() + guildSession.MusicPlayer.Close <- struct{}{} + tempVoiceConn.Disconnect() + delete(GuildSessionMap, m.GuildID) + + s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s left '%s'", m.Author.Mention(), channel.Name)) + } else { + s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%s not in voice channel", m.Author.Mention())) + } +} From 912c7cc16e520ed38ae6a688f693dae77489bce0 Mon Sep 17 00:00:00 2001 From: Duncan Leo Date: Mon, 14 Oct 2019 23:05:03 +0800 Subject: [PATCH 4/5] remove debug print --- commands/player.go | 1 - 1 file changed, 1 deletion(-) diff --git a/commands/player.go b/commands/player.go index 034fafe..5b7435d 100644 --- a/commands/player.go +++ b/commands/player.go @@ -269,7 +269,6 @@ func SafeCheckPlay(guildSession *GuildSession) { } guildSession.Mutex.Lock() guildSession.Queue = append(guildSession.Queue, queueItem) - log.Printf("SCP %+v\n", GuildSessionMap) guildSession.Mutex.Unlock() } guildSession.Mutex.Lock() From 4b1462646b4ac32ab81f9033778d3b1e110093cb Mon Sep 17 00:00:00 2001 From: Duncan Leo Date: Mon, 14 Oct 2019 23:44:09 +0800 Subject: [PATCH 5/5] move game status updating to goroutine --- commands/commands.go | 11 +++++------ commands/player.go | 2 -- main.go | 40 +++++++++++++++++++++++++++++++++++----- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/commands/commands.go b/commands/commands.go index 12d6806..34b7022 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -27,6 +27,7 @@ type MusicPlayer struct { // GuildSession represents a guild voice session type GuildSession struct { + GuildID string Mutex sync.Mutex Queue []models.QueueItem // current item = index 0 VoiceConnection *discordgo.VoiceConnection @@ -34,9 +35,10 @@ type GuildSession struct { MusicPlayer MusicPlayer } -func newGuildSession() GuildSession { +func newGuildSession(guildID string) GuildSession { return GuildSession{ - Mutex: sync.Mutex{}, + GuildID: guildID, + Mutex: sync.Mutex{}, MusicPlayer: MusicPlayer{ Close: make(chan struct{}), Control: make(chan ControlMessage), @@ -51,14 +53,11 @@ func safeGetGuildSession(guildID string) *GuildSession { if session, ok := GuildSessionMap[guildID]; ok { return session } - session := newGuildSession() + session := newGuildSession(guildID) GuildSessionMap[guildID] = &session return &session } -// GameUpdateFunc call to update the bot's current game -var GameUpdateFunc func(game string) - var youtubeService *youtube.Service func init() { diff --git a/commands/player.go b/commands/player.go index 5b7435d..ef06add 100644 --- a/commands/player.go +++ b/commands/player.go @@ -274,13 +274,11 @@ func SafeCheckPlay(guildSession *GuildSession) { guildSession.Mutex.Lock() var song = guildSession.Queue[0] guildSession.Mutex.Unlock() - GameUpdateFunc("with myself") if ttsMsgURL, err := googletts.GetTTSURL(fmt.Sprintf("Music: %s", sanitiseSongTitle(song.Title)), "en"); err == nil { log.Println("[PLAYER] Announcing upcoming song title") guildSession.Play(ttsMsgURL, "0.5") } - GameUpdateFunc(fmt.Sprintf("%s (%s)", song.Title, song.ChannelTitle)) log.Println("[PLAYER] Playing the actual song data") guildSession.Play(fmt.Sprintf("https://www.youtube.com/watch?v=%s", song.VideoID), os.Getenv("BOT_VOLUME")) guildSession.Mutex.Lock() diff --git a/main.go b/main.go index c2bd3e5..26c7320 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "os/signal" "strings" "syscall" + "time" "github.com/bottleneckco/discord-radio/commands" "github.com/bottleneckco/discord-radio/util" @@ -22,6 +23,37 @@ func main() { log.Panic(err) } + gameStatusQuitChannel := make(chan bool) + + go func() { + for { + select { + case <-gameStatusQuitChannel: + return + default: + // Update music status + if len(commands.GuildSessionMap) == 0 { + dg.UpdateStatus(1, "") + } else { + var sb strings.Builder + for _, guildSession := range commands.GuildSessionMap { + if len(guildSession.Queue) > 0 && guildSession.MusicPlayer.IsPlaying { + guild, err := dg.Guild(guildSession.GuildID) + if err != nil { + log.Println(err) + continue + } + song := guildSession.Queue[0] + sb.WriteString(fmt.Sprintf("[%s] %s (%s) | ", guild.Name, song.Title, song.ChannelTitle)) + } + } + dg.UpdateStatus(0, sb.String()) + } + } + time.Sleep(5 * time.Second) + } + }() + dg.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { // Ignore all messages created by the bot itself if m.Author.ID == s.State.User.ID { @@ -82,7 +114,7 @@ func main() { } } - if len(util.GetUsersInVoiceChannel(s, guildSession.VoiceConnection.ChannelID)) == 1 { + if len(util.GetUsersInVoiceChannel(s, guildSession.VoiceConnection.ChannelID)) == 9 { // Only bot left log.Println("Leaving, only me left in voice channel.") s.UpdateStatus(1, "") @@ -97,10 +129,6 @@ func main() { } }) - commands.GameUpdateFunc = func(game string) { - dg.UpdateStatus(0, game) - } - err = dg.Open() if err != nil { log.Panic(err) @@ -118,6 +146,8 @@ func main() { } } + gameStatusQuitChannel <- true + // Cleanly close down the Discord session. dg.Close() }