diff --git a/yammer.go b/bridge.go similarity index 69% rename from yammer.go rename to bridge.go index 7bfb6de..1884066 100644 --- a/yammer.go +++ b/bridge.go @@ -22,7 +22,7 @@ func startBridge(discord *discordgo.Session, discordGID string, discordCID strin } defer dgv.Speaking(false) defer dgv.Close() - + Bridge.Connected = true discord.ShouldReconnectOnError = true // MUMBLE Setup @@ -79,41 +79,37 @@ func startBridge(discord *discordgo.Session, discordGID string, discordCID strin log.Printf("\nGot %s signal. Terminating Mumble-Bridge\n", sig) case <-die: log.Println("\nGot internal die request. Terminating Mumble-Bridge") - close(toMumble) dgv.Disconnect() - log.Println("Closing mumble threads") det.Detach() + Bridge.Connected = false } } func pingMumble(host, port string, c chan int) { m, _ := time.ParseDuration("30s") curr := 0 - log.Println("Started mumble ping loop") for { time.Sleep(3 * time.Second) resp, err := gumble.Ping(host+":"+port, -1, m) + curr = resp.ConnectedUsers if err != nil { panic(err) } - if resp.ConnectedUsers-1 != curr { - curr = resp.ConnectedUsers - 1 - log.Printf("Now %v users in mumble\n", curr) - if curr > 0 { - c <- curr - } + if Bridge.Connected { + curr = curr - 1 + } + if curr != Bridge.MumbleUserCount { + Bridge.MumbleUserCount = curr + c <- Bridge.MumbleUserCount } } - log.Println("Mumble ping loop broken") } func discordStatusUpdate(dg *discordgo.Session, c chan int) { status := "" curr := 0 - log.Println("Started discord control loop") for { curr = <-c - log.Println("Updating discord status") if curr == 0 { status = "" } else { @@ -121,5 +117,28 @@ func discordStatusUpdate(dg *discordgo.Session, c chan int) { } dg.UpdateListeningStatus(status) } - log.Println("Discord control loop broken") +} + +func AutoBridge(s *discordgo.Session) { + log.Println("beginning auto mode") + for { + select { + default: + case <-Bridge.AutoChan: + log.Println("ending automode") + return + } + time.Sleep(3 * time.Second) + if !Bridge.Connected && Bridge.MumbleUserCount > 0 && Bridge.DiscordUserCount > 0 { + log.Println("users detected in mumble and discord, bridging") + die := make(chan bool) + Bridge.ActiveConn = die + go startBridge(s, BridgeConf.GID, BridgeConf.CID, BridgeConf.Config, BridgeConf.MumbleAddr, BridgeConf.MumbleInsecure, die) + } + log.Printf("DU: %v MU %v\n", Bridge.DiscordUserCount, Bridge.MumbleUserCount) + if Bridge.Connected && Bridge.MumbleUserCount == 0 && Bridge.DiscordUserCount <= 1 { + log.Println("no one online, killing bridge") + Bridge.ActiveConn <- true + } + } } diff --git a/config.go b/config.go index ce6993e..295f809 100644 --- a/config.go +++ b/config.go @@ -10,12 +10,22 @@ import ( "layeh.com/gumble/gumble" ) -type YammerConfig struct { +type BridgeConfig struct { Config *gumble.Config MumbleAddr string MumbleInsecure bool - Die chan bool - ActiveConns map[string]chan bool + Auto bool + Command string + GID string + CID string +} + +type BridgeState struct { + ActiveConn chan bool + Connected bool + MumbleUserCount int + DiscordUserCount int + AutoChan chan bool } func lookupEnvOrString(key string, defaultVal string) string { diff --git a/discord.go b/discord.go index 15a7ac4..966c66f 100644 --- a/discord.go +++ b/discord.go @@ -98,7 +98,6 @@ func discordSendPCM(v *discordgo.VoiceConnection, pcm <-chan []int16, die chan b lastReady = true readyTimeout.Stop() } - log.Println("sending packets to mumble") v.OpusSend <- opus } else { if streaming { @@ -118,14 +117,12 @@ func discordReceivePCM(v *discordgo.VoiceConnection, die chan bool) { var readyTimeout *time.Timer for { - log.Println("Start loop") select { case <-die: log.Println("killing discord ReceivePCM") return default: } - log.Println("checked for death") if v.Ready == false || v.OpusRecv == nil { if lastReady == true { OnError(fmt.Sprintf("Discordgo not to receive opus packets. %+v : %+v", v.Ready, v.OpusSend), nil) @@ -142,16 +139,13 @@ func discordReceivePCM(v *discordgo.VoiceConnection, die chan bool) { readyTimeout.Stop() } - log.Println("rec1") p, ok := <-v.OpusRecv - log.Println("rec2") if !ok { log.Println("Opus not ok") continue } discordMutex.Lock() - log.Println("got lock one") _, ok = fromDiscordMap[p.SSRC] discordMutex.Unlock() if !ok { @@ -169,20 +163,28 @@ func discordReceivePCM(v *discordgo.VoiceConnection, die chan bool) { } discordMutex.Lock() - log.Println("got lock 2") p.PCM, err = fromDiscordMap[p.SSRC].decoder.Decode(p.Opus, 960, false) discordMutex.Unlock() if err != nil { OnError("Error decoding opus data", err) continue } - + if len(p.PCM) != 960 { + log.Println("Opus size error") + continue + } discordMutex.Lock() - log.Println("got lock 3") - fromDiscordMap[p.SSRC].pcm <- p.PCM[0:480] - fromDiscordMap[p.SSRC].pcm <- p.PCM[480:960] + select { + case fromDiscordMap[p.SSRC].pcm <- p.PCM[0:480]: + default: + log.Println("fromDiscordMap buffer full. Dropping packet") + } + select { + case fromDiscordMap[p.SSRC].pcm <- p.PCM[480:960]: + default: + log.Println("fromDiscordMap buffer full. Dropping packet") + } discordMutex.Unlock() - log.Println("finish loop") } } @@ -237,7 +239,6 @@ func fromDiscordMixer(toMumble chan<- gumble.AudioBuffer, die chan bool) { if sendAudio { select { case toMumble <- outBuf: - log.Println("sending to mumble") default: log.Println("toMumble buffer full. Dropping packet") } diff --git a/handlers.go b/handlers.go index f275e05..4ce0c15 100644 --- a/handlers.go +++ b/handlers.go @@ -20,8 +20,8 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { if m.Author.ID == s.State.User.ID { return } - - if strings.HasPrefix(m.Content, "!yammer link") { + prefix := "!" + BridgeConf.Command + if strings.HasPrefix(m.Content, prefix+" link") { // Find the channel that the message came from. c, err := s.State.Channel(m.ChannelID) @@ -42,14 +42,14 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { if vs.UserID == m.Author.ID { log.Printf("Trying to join GID %v and VID %v\n", g.ID, vs.ChannelID) die := make(chan bool) - YBConfig.ActiveConns[vs.ChannelID] = die - go startBridge(s, g.ID, vs.ChannelID, YBConfig.Config, YBConfig.MumbleAddr, YBConfig.MumbleInsecure, die) + Bridge.ActiveConn = die + go startBridge(s, g.ID, vs.ChannelID, BridgeConf.Config, BridgeConf.MumbleAddr, BridgeConf.MumbleInsecure, die) return } } } - if strings.HasPrefix(m.Content, "!yammer unlink") { + if strings.HasPrefix(m.Content, prefix+" unlink") { // Find the channel that the message came from. c, err := s.State.Channel(m.ChannelID) @@ -69,8 +69,8 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { for _, vs := range g.VoiceStates { if vs.UserID == m.Author.ID { log.Printf("Trying to leave GID %v and VID %v\n", g.ID, vs.ChannelID) - YBConfig.ActiveConns[vs.ChannelID] <- true - YBConfig.ActiveConns[vs.ChannelID] = nil + Bridge.ActiveConn <- true + Bridge.ActiveConn = nil MumbleReset() DiscordReset() return @@ -78,7 +78,7 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { } } - if strings.HasPrefix(m.Content, "!yammer refresh") { + if strings.HasPrefix(m.Content, prefix+" refresh") { // Find the channel that the message came from. c, err := s.State.Channel(m.ChannelID) @@ -98,16 +98,27 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { for _, vs := range g.VoiceStates { if vs.UserID == m.Author.ID { log.Printf("Trying to refresh GID %v and VID %v\n", g.ID, vs.ChannelID) - YBConfig.ActiveConns[vs.ChannelID] <- true + Bridge.ActiveConn <- true MumbleReset() DiscordReset() time.Sleep(5 * time.Second) - YBConfig.ActiveConns[vs.ChannelID] = make(chan bool) - go startBridge(s, g.ID, vs.ChannelID, YBConfig.Config, YBConfig.MumbleAddr, YBConfig.MumbleInsecure, YBConfig.ActiveConns[vs.ChannelID]) + Bridge.ActiveConn = make(chan bool) + go startBridge(s, g.ID, vs.ChannelID, BridgeConf.Config, BridgeConf.MumbleAddr, BridgeConf.MumbleInsecure, Bridge.ActiveConn) return } } } + + if strings.HasPrefix(m.Content, prefix+" auto") { + if BridgeConf.Auto == false { + BridgeConf.Auto = true + Bridge.AutoChan = make(chan bool) + go AutoBridge(s) + } else { + Bridge.AutoChan <- true + BridgeConf.Auto = false + } + } } func guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) { @@ -123,3 +134,35 @@ func guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) { } } } + +func voiceUpdate(s *discordgo.Session, event *discordgo.VoiceStateUpdate) { + if event.GuildID == BridgeConf.GID { + if event.ChannelID == BridgeConf.CID { + log.Println("user joined watched discord channel") + Bridge.DiscordUserCount = Bridge.DiscordUserCount + 1 + } + if event.ChannelID == "" { + //leave event, trigger recount of active users + //TODO when next version of discordgo comes out, switch to PreviousState + g, err := s.State.Guild(event.GuildID) + if err != nil { + // Could not find guild. + return + } + + // Look for current voice states in watched channel + count := 0 + for _, vs := range g.VoiceStates { + if vs.ChannelID == BridgeConf.CID { + count = count + 1 + } + } + if Bridge.DiscordUserCount > count { + log.Println("user left watched discord channel") + Bridge.DiscordUserCount = count + } + } + + } + return +} diff --git a/main.go b/main.go index 8ae4c58..9d53808 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,8 @@ import ( _ "layeh.com/gumble/opus" ) -var YBConfig *YammerConfig +var BridgeConf *BridgeConfig +var Bridge *BridgeState func main() { godotenv.Load() @@ -29,7 +30,7 @@ func main() { discordToken := flag.String("discord-token", lookupEnvOrString("DISCORD_TOKEN", ""), "DISCORD_TOKEN, discord bot token") discordGID := flag.String("discord-gid", lookupEnvOrString("DISCORD_GID", ""), "DISCORD_GID, discord gid") discordCID := flag.String("discord-cid", lookupEnvOrString("DISCORD_CID", ""), "DISCORD_CID, discord cid") - + discordCommand := flag.String("discord-command", lookupEnvOrString("DISCORD_COMMAND", "mumble-discord"), "Discord command string, env alt DISCORD_COMMAND, optional, defaults to mumble-discord") flag.Parse() log.Printf("app.config %v\n", getConfig(flag.CommandLine)) @@ -66,6 +67,7 @@ func main() { discord.AddHandler(ready) discord.AddHandler(messageCreate) discord.AddHandler(guildCreate) + discord.AddHandler(voiceUpdate) err = discord.Open() if err != nil { log.Println(err) @@ -74,19 +76,30 @@ func main() { defer discord.Close() log.Println("Discord Bot Connected") + log.Printf("Discord bot looking for command !%v", *discordCommand) config := gumble.NewConfig() config.Username = *mumbleUsername config.Password = *mumblePassword config.AudioInterval = time.Millisecond * 10 - YBConfig = &YammerConfig{ + BridgeConf = &BridgeConfig{ Config: config, MumbleAddr: *mumbleAddr + ":" + strconv.Itoa(*mumblePort), MumbleInsecure: *mumbleInsecure, - ActiveConns: make(map[string]chan bool), + Auto: false, + Command: *discordCommand, + GID: *discordGID, + CID: *discordCID, } - - //go startBridge(discord, *discordGID, *discordCID, config, *mumbleAddr+":"+strconv.Itoa(*mumblePort), *mumbleInsecure, die) + Bridge = &BridgeState{ + ActiveConn: make(chan bool), + Connected: false, + MumbleUserCount: 0, + DiscordUserCount: 0, + } + userCount := make(chan int) + go pingMumble(*mumbleAddr, strconv.Itoa(*mumblePort), userCount) + go discordStatusUpdate(discord, userCount) sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)