From 4717bdec27e879b47970e54896071da0ff73b2f8 Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 26 Jan 2021 10:33:47 -0500 Subject: [PATCH 01/20] on mumble connect failure disconnect from discord too --- bridge.go | 1 + 1 file changed, 1 insertion(+) diff --git a/bridge.go b/bridge.go index 9d1c980..7ea2e52 100644 --- a/bridge.go +++ b/bridge.go @@ -98,6 +98,7 @@ func (b *BridgeState) startBridge() { if err != nil { log.Println(err) + b.DiscordVoice.Disconnect() return } defer b.MumbleClient.Disconnect() From a58a7197a9249f6dac55134fc26b493fe42e4406 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Mon, 1 Feb 2021 16:03:38 -0500 Subject: [PATCH 02/20] clean up bridge terminate --- bridge.go | 96 +++++++++++++++++++++++++++------------------ discord-handlers.go | 5 +-- discord.go | 33 +++++++++------- main.go | 30 ++++++++++---- mumble-handlers.go | 5 +++ mumble.go | 20 ++++------ 6 files changed, 114 insertions(+), 75 deletions(-) diff --git a/bridge.go b/bridge.go index 7ea2e52..6e6bac5 100644 --- a/bridge.go +++ b/bridge.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/tls" "fmt" "log" @@ -24,9 +25,15 @@ type BridgeState struct { // The configuration data for this bridge BridgeConfig *BridgeConfig - // TODO + // External requests to kill the bridge BridgeDie chan bool + // Lock to only allow one bridge session at a time + lock sync.Mutex + + // Wait for bridge to exit cleanly + WaitExit *sync.WaitGroup + // Bridge connection Connected bool @@ -67,33 +74,44 @@ type BridgeState struct { // startBridge established the voice connection func (b *BridgeState) startBridge() { + b.lock.Lock() + defer b.lock.Unlock() b.BridgeDie = make(chan bool) + defer close(b.BridgeDie) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + wg := sync.WaitGroup{} + b.WaitExit = &wg var err error // DISCORD Connect Voice - + log.Println("Attempting to join Discord voice channel") b.DiscordVoice, err = b.DiscordSession.ChannelVoiceJoin(b.BridgeConfig.GID, b.BridgeConfig.CID, false, false) if err != nil { log.Println(err) + b.DiscordVoice.Disconnect() return } + defer b.DiscordVoice.Disconnect() defer b.DiscordVoice.Speaking(false) - defer b.DiscordVoice.Close() + log.Println("Discord Voice Connected") // MUMBLE Connect - b.MumbleStream = &MumbleDuplex{ - die: b.BridgeDie, - } + b.MumbleStream = &MumbleDuplex{} det := b.BridgeConfig.MumbleConfig.AudioListeners.Attach(b.MumbleStream) + defer det.Detach() var tlsConfig tls.Config if b.BridgeConfig.MumbleInsecure { tlsConfig.InsecureSkipVerify = true } + log.Println("Attempting to join Mumble") b.MumbleClient, err = gumble.DialWithDialer(new(net.Dialer), b.BridgeConfig.MumbleAddr, b.BridgeConfig.MumbleConfig, &tlsConfig) if err != nil { @@ -102,6 +120,7 @@ func (b *BridgeState) startBridge() { return } defer b.MumbleClient.Disconnect() + log.Println("Mumble Connected") // Shared Channels // Shared channels pass PCM information in 10ms chunks [480]int16 @@ -109,65 +128,66 @@ func (b *BridgeState) startBridge() { var toMumble = b.MumbleClient.AudioOutgoing() var toDiscord = make(chan []int16, 100) - log.Println("Mumble Connected") + defer close(toDiscord) + defer close(toMumble) // Start Passing Between // From Mumble - go b.MumbleStream.fromMumbleMixer(toDiscord, b.BridgeDie) + go b.MumbleStream.fromMumbleMixer(ctx, &wg, toDiscord) // From Discord b.DiscordStream = &DiscordDuplex{ Bridge: b, fromDiscordMap: make(map[uint32]fromDiscord), - die: b.BridgeDie, } - go b.DiscordStream.discordReceivePCM() - go b.DiscordStream.fromDiscordMixer(toMumble) + go b.DiscordStream.discordReceivePCM(ctx, &wg, cancel) + go b.DiscordStream.fromDiscordMixer(ctx, &wg, toMumble) // To Discord - go b.DiscordStream.discordSendPCM(toDiscord) + go b.DiscordStream.discordSendPCM(ctx, &wg, cancel, toDiscord) + // Monitor Mumble go func() { + wg.Add(1) ticker := time.NewTicker(500 * time.Millisecond) for { - <-ticker.C - if b.MumbleClient == nil || b.MumbleClient.State() != 2 { - if b.MumbleClient != nil { - log.Println("Lost mumble connection " + strconv.Itoa(int(b.MumbleClient.State()))) - } else { - log.Println("Lost mumble connection due to bridge dieing") - return + select { + case <-ticker.C: + if b.MumbleClient == nil || b.MumbleClient.State() != 2 { + if b.MumbleClient != nil { + log.Println("Lost mumble connection " + strconv.Itoa(int(b.MumbleClient.State()))) + } else { + log.Println("Lost mumble connection due to bridge dieing") + } + cancel() } - select { - case <-b.BridgeDie: - //die is already closed - - default: - close(b.BridgeDie) - } - + case <-ctx.Done(): + wg.Done() + return } } }() b.Connected = true + // Hold until cancelled or external die request select { + case <-ctx.Done(): + log.Println("Bridge internal context cancel") case <-b.BridgeDie: - log.Println("\nGot internal die request. Terminating Mumble-Bridge") - b.DiscordVoice.Disconnect() - det.Detach() - close(toDiscord) - close(toMumble) - close(b.BridgeDie) - b.Connected = false - b.DiscordVoice = nil - b.MumbleClient = nil - b.MumbleUsers = make(map[string]bool) - b.DiscordUsers = make(map[string]discordUser) + log.Println("Bridge die request received") + cancel() } + + b.Connected = false + wg.Wait() + log.Println("Terminating Bridge") + b.MumbleUsersMutex.Lock() + b.MumbleUsers = make(map[string]bool) + b.MumbleUsersMutex.Unlock() + b.DiscordUsers = make(map[string]discordUser) } func (b *BridgeState) discordStatusUpdate() { diff --git a/discord-handlers.go b/discord-handlers.go index 002edf2..ac46684 100644 --- a/discord-handlers.go +++ b/discord-handlers.go @@ -100,7 +100,6 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa if vs.UserID == m.Author.ID { log.Printf("Trying to leave GID %v and VID %v\n", g.ID, vs.ChannelID) l.Bridge.BridgeDie <- true - l.Bridge.BridgeDie = nil return } } @@ -167,7 +166,7 @@ func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.Voi continue } - println("User joined Discord " + u.Username) + log.Println("User joined Discord " + u.Username) dm, err := s.UserChannelCreate(u.ID) if err != nil { log.Println("Error creating private channel for", u.Username) @@ -194,7 +193,7 @@ func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.Voi // Remove users that are no longer connected for id := range l.Bridge.DiscordUsers { if l.Bridge.DiscordUsers[id].seen == false { - println("User left Discord channel " + l.Bridge.DiscordUsers[id].username) + log.Println("User left Discord channel " + l.Bridge.DiscordUsers[id].username) if l.Bridge.Connected && !l.Bridge.BridgeConfig.MumbleDisableText { l.Bridge.MumbleClient.Do(func() { l.Bridge.MumbleClient.Self.Channel.Send(fmt.Sprintf("%v has left Discord channel\n", l.Bridge.DiscordUsers[id].username), false) diff --git a/discord.go b/discord.go index 4224d5a..44befca 100644 --- a/discord.go +++ b/discord.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "log" "sync" @@ -25,8 +26,6 @@ type DiscordDuplex struct { discordMutex sync.Mutex discordMixerMutex sync.Mutex fromDiscordMap map[uint32]fromDiscord - - die chan bool } // OnError gets called by dgvoice when an error is encountered. @@ -43,7 +42,7 @@ var OnError = func(str string, err error) { // SendPCM will receive on the provied channel encode // received PCM data into Opus then send that to Discordgo -func (dd *DiscordDuplex) discordSendPCM(pcm <-chan []int16) { +func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, cancel context.CancelFunc, pcm <-chan []int16) { const channels int = 1 const frameRate int = 48000 // audio sampling rate const frameSize int = 960 // uint16 size of each audio frame @@ -62,10 +61,12 @@ func (dd *DiscordDuplex) discordSendPCM(pcm <-chan []int16) { lastReady := true var readyTimeout *time.Timer + wg.Add(1) + for { select { - case <-dd.die: - log.Println("Killing discordSendPCM") + case <-ctx.Done(): + wg.Done() return default: } @@ -90,7 +91,8 @@ func (dd *DiscordDuplex) discordSendPCM(pcm <-chan []int16) { if lastReady == true { OnError(fmt.Sprintf("Discordgo not ready for opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil) readyTimeout = time.AfterFunc(30*time.Second, func() { - dd.die <- true + log.Println("set ready timeout") + cancel() }) lastReady = false } @@ -112,19 +114,21 @@ func (dd *DiscordDuplex) discordSendPCM(pcm <-chan []int16) { // ReceivePCM will receive on the the Discordgo OpusRecv channel and decode // the opus audio into PCM then send it on the provided channel. -func (dd *DiscordDuplex) discordReceivePCM() { +func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGroup, cancel context.CancelFunc) { var err error lastReady := true var readyTimeout *time.Timer + wg.Add(1) + for { if dd.Bridge.DiscordVoice.Ready == false || dd.Bridge.DiscordVoice.OpusRecv == nil { if lastReady == true { OnError(fmt.Sprintf("Discordgo not to receive opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil) readyTimeout = time.AfterFunc(30*time.Second, func() { log.Println("set ready timeout") - dd.die <- true + cancel() }) lastReady = false } @@ -139,10 +143,10 @@ func (dd *DiscordDuplex) discordReceivePCM() { var p *discordgo.Packet select { - case p, ok = <-dd.Bridge.DiscordVoice.OpusRecv: - case <-dd.die: - log.Println("killing discord ReceivePCM") + case <-ctx.Done(): + wg.Done() return + case p, ok = <-dd.Bridge.DiscordVoice.OpusRecv: } if !ok { @@ -196,14 +200,15 @@ func (dd *DiscordDuplex) discordReceivePCM() { } } -func (dd *DiscordDuplex) fromDiscordMixer(toMumble chan<- gumble.AudioBuffer) { +func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGroup, toMumble chan<- gumble.AudioBuffer) { ticker := time.NewTicker(10 * time.Millisecond) sendAudio := false + wg.Add(1) for { select { - case <-dd.die: - log.Println("killing fromDiscordMixer") + case <-ctx.Done(): + wg.Done() return case <-ticker.C: } diff --git a/main.go b/main.go index 52c6a33..3474313 100644 --- a/main.go +++ b/main.go @@ -112,17 +112,16 @@ func main() { } // MUMBLE SETUP - MumbleConfig := gumble.NewConfig() - Bridge.BridgeConfig.MumbleConfig = MumbleConfig - MumbleConfig.Username = *mumbleUsername - MumbleConfig.Password = *mumblePassword - MumbleConfig.AudioInterval = time.Millisecond * 10 + Bridge.BridgeConfig.MumbleConfig = gumble.NewConfig() + Bridge.BridgeConfig.MumbleConfig.Username = *mumbleUsername + Bridge.BridgeConfig.MumbleConfig.Password = *mumblePassword + Bridge.BridgeConfig.MumbleConfig.AudioInterval = time.Millisecond * 10 Bridge.MumbleListener = &MumbleListener{ Bridge: Bridge, } - MumbleConfig.Attach(gumbleutil.Listener{ + Bridge.BridgeConfig.MumbleConfig.Attach(gumbleutil.Listener{ Connect: Bridge.MumbleListener.mumbleConnect, UserChange: Bridge.MumbleListener.mumbleUserChange, }) @@ -171,7 +170,12 @@ func main() { case "constant": log.Println("bridge starting in constant mode") Bridge.Mode = bridgeModeConstant - go Bridge.startBridge() + go func() { + for { + Bridge.startBridge() + log.Println("Bridge died. Restarting") + } + }() default: Bridge.DiscordSession.Close() log.Fatalln("invalid bridge mode set") @@ -179,8 +183,18 @@ func main() { go Bridge.discordStatusUpdate() + // Shutdown on OS signal sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) <-sc - log.Println("Bot shutting down") + + // Signal the bridge to exit cleanly + Bridge.BridgeDie <- true + + log.Println("OS Signal. Bot shutting down") + + // Wait or the bridge to exit cleanly + if Bridge.Connected { + Bridge.WaitExit.Wait() + } } diff --git a/mumble-handlers.go b/mumble-handlers.go index cb4ead0..860d5da 100644 --- a/mumble-handlers.go +++ b/mumble-handlers.go @@ -1,6 +1,7 @@ package main import ( + "log" "strings" "layeh.com/gumble/gumble" @@ -38,6 +39,8 @@ func (l *MumbleListener) mumbleUserChange(e *gumble.UserChangeEvent) { if e.Type.Has(gumble.UserChangeConnected) { + log.Println("User connected to mumble " + e.User.Name) + if !l.Bridge.BridgeConfig.MumbleDisableText { e.User.Send("Mumble-Discord-Bridge v" + version) @@ -63,7 +66,9 @@ func (l *MumbleListener) mumbleUserChange(e *gumble.UserChangeEvent) { // Send discord a notice l.Bridge.discordSendMessageAll(e.User.Name + " has joined mumble") } + if e.Type.Has(gumble.UserChangeDisconnected) { l.Bridge.discordSendMessageAll(e.User.Name + " has left mumble") + log.Println("User disconnected from mumble " + e.User.Name) } } diff --git a/mumble.go b/mumble.go index 8912989..4f99c70 100644 --- a/mumble.go +++ b/mumble.go @@ -1,6 +1,7 @@ package main import ( + "context" "log" "sync" "time" @@ -14,9 +15,7 @@ var fromMumbleArr []chan gumble.AudioBuffer var mumbleStreamingArr []bool // MumbleDuplex - listenera and outgoing -type MumbleDuplex struct { - die chan bool -} +type MumbleDuplex struct{} // OnAudioStream - Spawn routines to handle incoming packets func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { @@ -30,12 +29,10 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { mutex.Unlock() go func() { + // TODO kill go routine on cleanup log.Println("new mumble audio stream", e.User.Name) for { select { - case <-m.die: - log.Println("Removing mumble audio stream") - return case p := <-e.C: // log.Println("audio packet", p.Sender.Name, len(p.AudioBuffer)) @@ -49,17 +46,19 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { return } -func (m MumbleDuplex) fromMumbleMixer(toDiscord chan []int16, die chan bool) { +func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, toDiscord chan []int16) { ticker := time.NewTicker(10 * time.Millisecond) sendAudio := false + wg.Add(1) for { select { - case <-die: - log.Println("Killing fromMumbleMixer") + case <-ctx.Done(): + wg.Done() return default: } + <-ticker.C mutex.Lock() @@ -99,9 +98,6 @@ func (m MumbleDuplex) fromMumbleMixer(toDiscord chan []int16, die chan bool) { if sendAudio { select { case toDiscord <- outBuf: - case <-die: - log.Println("Killing fromMumbleMixer") - return default: log.Println("toDiscord buffer full. Dropping packet") } From 7b20d39dcc2f11cccf11bed7b59f4bd3763ff4ea Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Mon, 1 Feb 2021 16:11:12 -0500 Subject: [PATCH 03/20] update docker build --- .dockerignore | 2 ++ Dockerfile | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8c5bcf6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.env +dist \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 0420057..da01798 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,15 +5,15 @@ FROM golang:1.15 as builder WORKDIR /go/src/app COPY . . - -RUN go build -o mumble-discord-bridge -ldflags="-extldflags=-static" *.go +RUN curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh +RUN ./bin/goreleaser build --skip-validate # Stage 2 FROM alpine:latest as static WORKDIR /opt/ RUN apk add opus -COPY --from=builder /go/src/app/mumble-discord-bridge . +COPY --from=builder /go/src/app/dist/mumble-discord-bridge_linux_amd64/mumble-discord-bridge . # Entry Point CMD ["/opt/mumble-discord-bridge"] From 7094f71f437e7c35d50c54ce4ba6791f68a148d0 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Mon, 1 Feb 2021 17:43:43 -0500 Subject: [PATCH 04/20] static build use netgo --- .goreleaser.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 8a1834f..8d2e246 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -7,8 +7,11 @@ before: # you may remove this if you don't need go generate # - go generate ./... builds: - - env: + - ldflags: '-s -w -linkmode external -extldflags "-static" -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser' + env: - CGO_ENABLED=1 + flags: + - -tags=netgo goos: - linux # - windows From e89cd0fff7cb0f32205e7cc6830bda5b57fd5c7e Mon Sep 17 00:00:00 2001 From: jorgror <60797691+jorgror@users.noreply.github.com> Date: Sun, 7 Feb 2021 18:53:54 +0100 Subject: [PATCH 05/20] Info from issue #12 --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 465ffd5..bbb6fa6 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,23 @@ The guide below provides information on how to setup a Discord bot. [Create a Discord Bot](https://discordpy.readthedocs.io/en/latest/discord.html) -Individual Discord servers need to invite the bot before it can connect. +Individual Discord servers need to invite the bot before it can connect. +The bot requires the following permissions: +* View Channels +* See Messages +* Read Message History +* Voice Channel Connect +* Voice Channel Speak +* Voice Channel Use Voice Activity + + +### Finding Discord CID and GID + +Discord GID is a unique ID linked to one Discord Server, also called Guild. CID is similarly a unique ID for a Discord Channel. To find these you need to set Discord into developer Mode. + +[Instructions to enable Discord Developer Mode](https://discordia.me/en/developer-mode) + +Then you can get the GID by right-clicking your server and selecting Copy-ID. Similarly the CID can be found right clicking the voice channel and selecting Copy ID. ### Binary @@ -132,6 +148,7 @@ go build -o mumble-discord-bridge *.go make mumble-discord-bridge ``` + ## Known Issues Currently there is an issue opening the discord voice channel. From 25b769946626ba545f6182be6cc185bd7e4abd51 Mon Sep 17 00:00:00 2001 From: jorgror <60797691+jorgror@users.noreply.github.com> Date: Sun, 7 Feb 2021 18:57:19 +0100 Subject: [PATCH 06/20] Removed extra lines --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index bbb6fa6..da12769 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,6 @@ The bot requires the following permissions: * Voice Channel Speak * Voice Channel Use Voice Activity - ### Finding Discord CID and GID Discord GID is a unique ID linked to one Discord Server, also called Guild. CID is similarly a unique ID for a Discord Channel. To find these you need to set Discord into developer Mode. @@ -148,7 +147,6 @@ go build -o mumble-discord-bridge *.go make mumble-discord-bridge ``` - ## Known Issues Currently there is an issue opening the discord voice channel. From bcf0b30d1e42e32779dd529777c82010228dad45 Mon Sep 17 00:00:00 2001 From: Steve Date: Mon, 8 Feb 2021 13:16:09 -0500 Subject: [PATCH 07/20] use string slice for mumble channel --- config.go | 2 +- main.go | 3 ++- mumble-handlers.go | 13 ++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config.go b/config.go index 71148ab..e4df869 100644 --- a/config.go +++ b/config.go @@ -24,7 +24,7 @@ type BridgeConfig struct { MumbleConfig *gumble.Config MumbleAddr string MumbleInsecure bool - MumbleChannel string + MumbleChannel []string MumbleDisableText bool Command string GID string diff --git a/main.go b/main.go index 3474313..788e18b 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "os/signal" "runtime/pprof" "strconv" + "strings" "syscall" "time" @@ -99,7 +100,7 @@ func main() { // MumbleConfig: config, MumbleAddr: *mumbleAddr + ":" + strconv.Itoa(*mumblePort), MumbleInsecure: *mumbleInsecure, - MumbleChannel: *mumbleChannel, + MumbleChannel: strings.Split(*mumbleChannel, "/"), MumbleDisableText: *mumbleDisableText, Command: *discordCommand, GID: *discordGID, diff --git a/mumble-handlers.go b/mumble-handlers.go index 860d5da..8040b33 100644 --- a/mumble-handlers.go +++ b/mumble-handlers.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "strings" @@ -13,12 +14,10 @@ type MumbleListener struct { } func (l *MumbleListener) mumbleConnect(e *gumble.ConnectEvent) { - if l.Bridge.BridgeConfig.MumbleChannel != "" { - //join specified channel - startingChannel := e.Client.Channels.Find(l.Bridge.BridgeConfig.MumbleChannel) - if startingChannel != nil { - e.Client.Self.Move(startingChannel) - } + //join specified channel + startingChannel := e.Client.Channels.Find(l.Bridge.BridgeConfig.MumbleChannel...) + if startingChannel != nil { + e.Client.Self.Move(startingChannel) } } @@ -42,7 +41,7 @@ func (l *MumbleListener) mumbleUserChange(e *gumble.UserChangeEvent) { log.Println("User connected to mumble " + e.User.Name) if !l.Bridge.BridgeConfig.MumbleDisableText { - e.User.Send("Mumble-Discord-Bridge v" + version) + e.User.Send(fmt.Sprintf("Mumble-Discord-Bridge %v", version)) // Tell the user who is connected to discord if len(l.Bridge.DiscordUsers) == 0 { From 0a2a61b7df5c002889ad72b56e598a3c4fc4a617 Mon Sep 17 00:00:00 2001 From: Steve Date: Mon, 8 Feb 2021 13:25:48 -0500 Subject: [PATCH 08/20] add debug, update doc --- README.md | 4 +++- main.go | 5 +++-- mumble-handlers.go | 3 +-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 465ffd5..8ea5e34 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Usage of ./mumble-discord-bridge: -mumble-address string MUMBLE_ADDRESS, mumble server address, example example.com, required -mumble-channel string - MUMBLE_CHANNEL, mumble channel to start in, optional + MUMBLE_CHANNEL, mumble channel to start in, using '/' to seperate nested channels, optional -mumble-disable-text MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false) -mumble-insecure @@ -40,6 +40,8 @@ Usage of ./mumble-discord-bridge: MUMBLE_USERNAME, mumble username, (default: discord) (default "Discord") -nice NICE, whether the bridge should automatically try to 'nice' itself, (default false) + -debug + DEBUG_LEVEL, DISCORD debug level, optional (default: 1) ``` The bridge can be run with the follow modes: diff --git a/main.go b/main.go index 788e18b..b39e224 100644 --- a/main.go +++ b/main.go @@ -39,7 +39,7 @@ func main() { mumbleUsername := flag.String("mumble-username", lookupEnvOrString("MUMBLE_USERNAME", "Discord"), "MUMBLE_USERNAME, mumble username, (default: discord)") mumblePassword := flag.String("mumble-password", lookupEnvOrString("MUMBLE_PASSWORD", ""), "MUMBLE_PASSWORD, mumble password, optional") mumbleInsecure := flag.Bool("mumble-insecure", lookupEnvOrBool("MUMBLE_INSECURE", false), " MUMBLE_INSECURE, mumble insecure, optional") - mumbleChannel := flag.String("mumble-channel", lookupEnvOrString("MUMBLE_CHANNEL", ""), "MUMBLE_CHANNEL, mumble channel to start in, optional") + mumbleChannel := flag.String("mumble-channel", lookupEnvOrString("MUMBLE_CHANNEL", ""), "MUMBLE_CHANNEL, mumble channel to start in, using '/' to seperate nested channels, optional") mumbleDisableText := flag.Bool("mumble-disable-text", lookupEnvOrBool("MUMBLE_DISABLE_TEXT", false), "MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false)") discordToken := flag.String("discord-token", lookupEnvOrString("DISCORD_TOKEN", ""), "DISCORD_TOKEN, discord bot token, required") discordGID := flag.String("discord-gid", lookupEnvOrString("DISCORD_GID", ""), "DISCORD_GID, discord gid, required") @@ -48,6 +48,7 @@ func main() { discordDisableText := flag.Bool("discord-disable-text", lookupEnvOrBool("DISCORD_DISABLE_TEXT", false), "DISCORD_DISABLE_TEXT, disable sending direct messages to discord, (default false)") mode := flag.String("mode", lookupEnvOrString("MODE", "constant"), "MODE, [constant, manual, auto] determine which mode the bridge starts in, (default constant)") nice := flag.Bool("nice", lookupEnvOrBool("NICE", false), "NICE, whether the bridge should automatically try to 'nice' itself, (default false)") + debug := flag.Int("debug-level", lookupEnvOrInt("DEBUG", 1), "DEBUG_LEVEL, Discord debug level, optional, (default 1)") cpuprofile := flag.String("cpuprofile", "", "write cpu profile to `file`") @@ -136,7 +137,7 @@ func main() { return } - Bridge.DiscordSession.LogLevel = 1 + Bridge.DiscordSession.LogLevel = *debug Bridge.DiscordSession.StateEnabled = true Bridge.DiscordSession.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged) Bridge.DiscordSession.ShouldReconnectOnError = true diff --git a/mumble-handlers.go b/mumble-handlers.go index 8040b33..6350c68 100644 --- a/mumble-handlers.go +++ b/mumble-handlers.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "log" "strings" @@ -41,7 +40,7 @@ func (l *MumbleListener) mumbleUserChange(e *gumble.UserChangeEvent) { log.Println("User connected to mumble " + e.User.Name) if !l.Bridge.BridgeConfig.MumbleDisableText { - e.User.Send(fmt.Sprintf("Mumble-Discord-Bridge %v", version)) + e.User.Send("Mumble-Discord-Bridge v" + version) // Tell the user who is connected to discord if len(l.Bridge.DiscordUsers) == 0 { From 8e5bb4b4794be8da144df8899235eaea92f64ada Mon Sep 17 00:00:00 2001 From: Steve Date: Sun, 7 Feb 2021 15:25:02 -0500 Subject: [PATCH 09/20] announce auto status change, respect what voice channel used for manual link --- bridge.go | 12 +++++++++--- discord-handlers.go | 6 ++++-- main.go | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/bridge.go b/bridge.go index 6e6bac5..0c71c6c 100644 --- a/bridge.go +++ b/bridge.go @@ -73,7 +73,8 @@ type BridgeState struct { } // startBridge established the voice connection -func (b *BridgeState) startBridge() { +// if channelID is empty, use channelID from config +func (b *BridgeState) startBridge(channelID string) { b.lock.Lock() defer b.lock.Unlock() @@ -90,7 +91,12 @@ func (b *BridgeState) startBridge() { // DISCORD Connect Voice log.Println("Attempting to join Discord voice channel") - b.DiscordVoice, err = b.DiscordSession.ChannelVoiceJoin(b.BridgeConfig.GID, b.BridgeConfig.CID, false, false) + if channelID == "" { + b.DiscordVoice, err = b.DiscordSession.ChannelVoiceJoin(b.BridgeConfig.GID, b.BridgeConfig.CID, false, false) + } else { + b.DiscordVoice, err = b.DiscordSession.ChannelVoiceJoin(b.BridgeConfig.GID, channelID, false, false) + } + if err != nil { log.Println(err) b.DiscordVoice.Disconnect() @@ -241,7 +247,7 @@ func (b *BridgeState) AutoBridge() { if !b.Connected && b.MumbleUserCount > 0 && len(b.DiscordUsers) > 0 { log.Println("users detected in mumble and discord, bridging") - go b.startBridge() + go b.startBridge("") } if b.Connected && b.MumbleUserCount == 0 && len(b.DiscordUsers) <= 1 { log.Println("no one online, killing bridge") diff --git a/discord-handlers.go b/discord-handlers.go index ac46684..ea23ea2 100644 --- a/discord-handlers.go +++ b/discord-handlers.go @@ -88,7 +88,7 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa for _, vs := range g.VoiceStates { if vs.UserID == m.Author.ID { log.Printf("Trying to join GID %v and VID %v\n", g.ID, vs.ChannelID) - go l.Bridge.startBridge() + go l.Bridge.startBridge(vs.ChannelID) return } } @@ -114,7 +114,7 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa time.Sleep(5 * time.Second) - go l.Bridge.startBridge() + go l.Bridge.startBridge(vs.ChannelID) return } } @@ -122,10 +122,12 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa if strings.HasPrefix(m.Content, prefix+" auto") { if l.Bridge.Mode != bridgeModeAuto { + l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Auto mode enabled") l.Bridge.Mode = bridgeModeAuto l.Bridge.AutoChanDie = make(chan bool) go l.Bridge.AutoBridge() } else { + l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Auto mode disabled") l.Bridge.AutoChanDie <- true l.Bridge.Mode = bridgeModeManual } diff --git a/main.go b/main.go index b39e224..3627262 100644 --- a/main.go +++ b/main.go @@ -174,7 +174,7 @@ func main() { Bridge.Mode = bridgeModeConstant go func() { for { - Bridge.startBridge() + Bridge.startBridge("") log.Println("Bridge died. Restarting") } }() From 027d970c117d04a421f03227d00b62b69d2f7b5f Mon Sep 17 00:00:00 2001 From: Steve Date: Sun, 7 Feb 2021 15:44:20 -0500 Subject: [PATCH 10/20] store channelid in state so other functions can use it --- bridge.go | 16 +++++++++------- discord-handlers.go | 11 +++++++---- main.go | 4 +++- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/bridge.go b/bridge.go index 0c71c6c..2e55cec 100644 --- a/bridge.go +++ b/bridge.go @@ -70,11 +70,13 @@ type BridgeState struct { // Mumble Duplex and Event Listener MumbleStream *MumbleDuplex MumbleListener *MumbleListener + + // Discord Voice channel to join + DiscordChannelID string } // startBridge established the voice connection -// if channelID is empty, use channelID from config -func (b *BridgeState) startBridge(channelID string) { +func (b *BridgeState) startBridge() { b.lock.Lock() defer b.lock.Unlock() @@ -91,11 +93,11 @@ func (b *BridgeState) startBridge(channelID string) { // DISCORD Connect Voice log.Println("Attempting to join Discord voice channel") - if channelID == "" { - b.DiscordVoice, err = b.DiscordSession.ChannelVoiceJoin(b.BridgeConfig.GID, b.BridgeConfig.CID, false, false) - } else { - b.DiscordVoice, err = b.DiscordSession.ChannelVoiceJoin(b.BridgeConfig.GID, channelID, false, false) + if b.DiscordChannelID == "" { + log.Println("Tried to start bridge but no Discord channel specified") + return } + b.DiscordVoice, err = b.DiscordSession.ChannelVoiceJoin(b.BridgeConfig.GID, b.DiscordChannelID, false, false) if err != nil { log.Println(err) @@ -247,7 +249,7 @@ func (b *BridgeState) AutoBridge() { if !b.Connected && b.MumbleUserCount > 0 && len(b.DiscordUsers) > 0 { log.Println("users detected in mumble and discord, bridging") - go b.startBridge("") + go b.startBridge() } if b.Connected && b.MumbleUserCount == 0 && len(b.DiscordUsers) <= 1 { log.Println("no one online, killing bridge") diff --git a/discord-handlers.go b/discord-handlers.go index ea23ea2..eb1800d 100644 --- a/discord-handlers.go +++ b/discord-handlers.go @@ -24,7 +24,7 @@ func (l *DiscordListener) guildCreate(s *discordgo.Session, event *discordgo.Gui } for _, vs := range event.VoiceStates { - if vs.ChannelID == l.Bridge.BridgeConfig.CID { + if vs.ChannelID == l.Bridge.DiscordChannelID { if s.State.User.ID == vs.UserID { // Ignore bot continue @@ -88,7 +88,8 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa for _, vs := range g.VoiceStates { if vs.UserID == m.Author.ID { log.Printf("Trying to join GID %v and VID %v\n", g.ID, vs.ChannelID) - go l.Bridge.startBridge(vs.ChannelID) + l.Bridge.DiscordChannelID = vs.ChannelID + go l.Bridge.startBridge() return } } @@ -114,7 +115,7 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa time.Sleep(5 * time.Second) - go l.Bridge.startBridge(vs.ChannelID) + go l.Bridge.startBridge() return } } @@ -124,10 +125,12 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa if l.Bridge.Mode != bridgeModeAuto { l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Auto mode enabled") l.Bridge.Mode = bridgeModeAuto + l.Bridge.DiscordChannelID = l.Bridge.BridgeConfig.CID l.Bridge.AutoChanDie = make(chan bool) go l.Bridge.AutoBridge() } else { l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Auto mode disabled") + l.Bridge.DiscordChannelID = "" l.Bridge.AutoChanDie <- true l.Bridge.Mode = bridgeModeManual } @@ -154,7 +157,7 @@ func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.Voi // Sync the channel voice states to the local discordUsersMap for _, vs := range g.VoiceStates { - if vs.ChannelID == l.Bridge.BridgeConfig.CID { + if vs.ChannelID == l.Bridge.DiscordChannelID { if s.State.User.ID == vs.UserID { // Ignore bot continue diff --git a/main.go b/main.go index 3627262..49efff9 100644 --- a/main.go +++ b/main.go @@ -165,6 +165,7 @@ func main() { log.Println("bridge starting in automatic mode") Bridge.AutoChanDie = make(chan bool) Bridge.Mode = bridgeModeAuto + Bridge.DiscordChannelID = Bridge.BridgeConfig.CID go Bridge.AutoBridge() case "manual": log.Println("bridge starting in manual mode") @@ -172,9 +173,10 @@ func main() { case "constant": log.Println("bridge starting in constant mode") Bridge.Mode = bridgeModeConstant + Bridge.DiscordChannelID = Bridge.BridgeConfig.CID go func() { for { - Bridge.startBridge("") + Bridge.startBridge() log.Println("Bridge died. Restarting") } }() From aac90dd11352179caee26b73fcef54185a68ce8c Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 9 Feb 2021 18:04:11 -0500 Subject: [PATCH 11/20] give feedback on commands --- discord-handlers.go | 24 +++++++++++++++++++----- main.go | 2 +- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/discord-handlers.go b/discord-handlers.go index eb1800d..5118693 100644 --- a/discord-handlers.go +++ b/discord-handlers.go @@ -61,10 +61,6 @@ func (l *DiscordListener) guildCreate(s *discordgo.Session, event *discordgo.Gui func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { - if l.Bridge.Mode == bridgeModeConstant { - return - } - // Ignore all messages created by the bot itself if m.Author.ID == s.State.User.ID { return @@ -83,9 +79,19 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa return } prefix := "!" + l.Bridge.BridgeConfig.Command + + if l.Bridge.Mode == bridgeModeConstant && strings.HasPrefix(m.Content, prefix) { + l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Constant mode enabled, manual commands can not be entered") + return + } + if strings.HasPrefix(m.Content, prefix+" link") { // Look for the message sender in that guild's current voice states. for _, vs := range g.VoiceStates { + if l.Bridge.Connected { + l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Bridge already running, unlink first") + return + } if vs.UserID == m.Author.ID { log.Printf("Trying to join GID %v and VID %v\n", g.ID, vs.ChannelID) l.Bridge.DiscordChannelID = vs.ChannelID @@ -97,8 +103,12 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa if strings.HasPrefix(m.Content, prefix+" unlink") { // Look for the message sender in that guild's current voice states. + if !l.Bridge.Connected { + l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Bridge is not currently running") + return + } for _, vs := range g.VoiceStates { - if vs.UserID == m.Author.ID { + if vs.UserID == m.Author.ID && vs.ChannelID == l.Bridge.DiscordChannelID { log.Printf("Trying to leave GID %v and VID %v\n", g.ID, vs.ChannelID) l.Bridge.BridgeDie <- true return @@ -108,6 +118,10 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa if strings.HasPrefix(m.Content, prefix+" refresh") { // Look for the message sender in that guild's current voice states. + if !l.Bridge.Connected { + l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Bridge is not currently running") + return + } 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) diff --git a/main.go b/main.go index 49efff9..5c2ddf4 100644 --- a/main.go +++ b/main.go @@ -193,7 +193,7 @@ func main() { <-sc // Signal the bridge to exit cleanly - Bridge.BridgeDie <- true + close(Bridge.BridgeDie) log.Println("OS Signal. Bot shutting down") From e85f521cb344cd5fea649222d0debbdfd295c0de Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 11 Feb 2021 14:37:34 -0500 Subject: [PATCH 12/20] kill bridge only when connected --- main.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 5c2ddf4..dc60c25 100644 --- a/main.go +++ b/main.go @@ -192,13 +192,12 @@ func main() { signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) <-sc - // Signal the bridge to exit cleanly - close(Bridge.BridgeDie) - log.Println("OS Signal. Bot shutting down") // Wait or the bridge to exit cleanly if Bridge.Connected { + //TODO BridgeDie occasionally panics on send to closed channel + Bridge.BridgeDie <- true Bridge.WaitExit.Wait() } } From 83e33b85bc2dea04b3d9080d5c6e4c5c38acd44d Mon Sep 17 00:00:00 2001 From: 2xsaiko Date: Thu, 25 Feb 2021 22:22:06 +0100 Subject: [PATCH 13/20] Add option to specify client certificate --- README.md | 8 ++++++++ bridge.go | 14 ++++++++++++++ config.go | 1 + main.go | 2 ++ 4 files changed, 25 insertions(+) diff --git a/README.md b/README.md index 1919f47..d575d03 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ Usage of ./mumble-discord-bridge: MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false) -mumble-insecure MUMBLE_INSECURE, mumble insecure, optional + -mumble-certificate + MUMBLE_CERTIFICATE, mumble client certificate, optional -mumble-password string MUMBLE_PASSWORD, mumble password, optional -mumble-port int @@ -93,6 +95,12 @@ Discord GID is a unique ID linked to one Discord Server, also called Guild. CID Then you can get the GID by right-clicking your server and selecting Copy-ID. Similarly the CID can be found right clicking the voice channel and selecting Copy ID. +### Generating Client Certificate + +If you don't have a client certificate, you can generate one with this command: + + openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout cert.pem -out cert.pem -subj "/CN=mumble-discord-bridge" + ### Binary Prebuilt binaries are available. diff --git a/bridge.go b/bridge.go index 2e55cec..d20b8fa 100644 --- a/bridge.go +++ b/bridge.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "net" + "os" "strconv" "sync" "time" @@ -119,6 +120,19 @@ func (b *BridgeState) startBridge() { tlsConfig.InsecureSkipVerify = true } + if b.BridgeConfig.MumbleCertificate != "" { + keyFile := "" + if keyFile == "" { + keyFile = b.BridgeConfig.MumbleCertificate + } + if certificate, err := tls.LoadX509KeyPair(b.BridgeConfig.MumbleCertificate, keyFile); err != nil { + fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err) + os.Exit(1) + } else { + tlsConfig.Certificates = append(tlsConfig.Certificates, certificate) + } + } + log.Println("Attempting to join Mumble") b.MumbleClient, err = gumble.DialWithDialer(new(net.Dialer), b.BridgeConfig.MumbleAddr, b.BridgeConfig.MumbleConfig, &tlsConfig) diff --git a/config.go b/config.go index e4df869..84b7558 100644 --- a/config.go +++ b/config.go @@ -24,6 +24,7 @@ type BridgeConfig struct { MumbleConfig *gumble.Config MumbleAddr string MumbleInsecure bool + MumbleCertificate string MumbleChannel []string MumbleDisableText bool Command string diff --git a/main.go b/main.go index dc60c25..c8a7b46 100644 --- a/main.go +++ b/main.go @@ -39,6 +39,7 @@ func main() { mumbleUsername := flag.String("mumble-username", lookupEnvOrString("MUMBLE_USERNAME", "Discord"), "MUMBLE_USERNAME, mumble username, (default: discord)") mumblePassword := flag.String("mumble-password", lookupEnvOrString("MUMBLE_PASSWORD", ""), "MUMBLE_PASSWORD, mumble password, optional") mumbleInsecure := flag.Bool("mumble-insecure", lookupEnvOrBool("MUMBLE_INSECURE", false), " MUMBLE_INSECURE, mumble insecure, optional") + mumbleCertificate := flag.String("mumble-certificate", lookupEnvOrString("MUMBLE_CERTIFICATE", ""), "MUMBLE_CERTIFICATE, client certificate to use when connecting to the Mumble server") mumbleChannel := flag.String("mumble-channel", lookupEnvOrString("MUMBLE_CHANNEL", ""), "MUMBLE_CHANNEL, mumble channel to start in, using '/' to seperate nested channels, optional") mumbleDisableText := flag.Bool("mumble-disable-text", lookupEnvOrBool("MUMBLE_DISABLE_TEXT", false), "MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false)") discordToken := flag.String("discord-token", lookupEnvOrString("DISCORD_TOKEN", ""), "DISCORD_TOKEN, discord bot token, required") @@ -101,6 +102,7 @@ func main() { // MumbleConfig: config, MumbleAddr: *mumbleAddr + ":" + strconv.Itoa(*mumblePort), MumbleInsecure: *mumbleInsecure, + MumbleCertificate: *mumbleCertificate, MumbleChannel: strings.Split(*mumbleChannel, "/"), MumbleDisableText: *mumbleDisableText, Command: *discordCommand, From 80d6b9f8e66a65c3df8bb692972bfaa359762c27 Mon Sep 17 00:00:00 2001 From: 2xsaiko Date: Fri, 5 Mar 2021 20:12:34 +0100 Subject: [PATCH 14/20] Apparently this uses tabs --- bridge.go | 4 ++-- main.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bridge.go b/bridge.go index d20b8fa..3ce6fc2 100644 --- a/bridge.go +++ b/bridge.go @@ -6,7 +6,7 @@ import ( "fmt" "log" "net" - "os" + "os" "strconv" "sync" "time" @@ -121,7 +121,7 @@ func (b *BridgeState) startBridge() { } if b.BridgeConfig.MumbleCertificate != "" { - keyFile := "" + keyFile := "" if keyFile == "" { keyFile = b.BridgeConfig.MumbleCertificate } diff --git a/main.go b/main.go index c8a7b46..e06bef2 100644 --- a/main.go +++ b/main.go @@ -102,7 +102,7 @@ func main() { // MumbleConfig: config, MumbleAddr: *mumbleAddr + ":" + strconv.Itoa(*mumblePort), MumbleInsecure: *mumbleInsecure, - MumbleCertificate: *mumbleCertificate, + MumbleCertificate: *mumbleCertificate, MumbleChannel: strings.Split(*mumbleChannel, "/"), MumbleDisableText: *mumbleDisableText, Command: *discordCommand, From 3d9f11ee562a77f83a34f6376b92e08e28cb03fe Mon Sep 17 00:00:00 2001 From: 2xsaiko Date: Fri, 5 Mar 2021 20:15:17 +0100 Subject: [PATCH 15/20] Cleanup --- bridge.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bridge.go b/bridge.go index 3ce6fc2..5630adf 100644 --- a/bridge.go +++ b/bridge.go @@ -121,11 +121,8 @@ func (b *BridgeState) startBridge() { } if b.BridgeConfig.MumbleCertificate != "" { - keyFile := "" - if keyFile == "" { - keyFile = b.BridgeConfig.MumbleCertificate - } - if certificate, err := tls.LoadX509KeyPair(b.BridgeConfig.MumbleCertificate, keyFile); err != nil { + keyFile := b.BridgeConfig.MumbleCertificate + if certificate, err := tls.LoadX509KeyPair(keyFile, keyFile); err != nil { fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err) os.Exit(1) } else { From 1ed35f6a39dc2ce6a8e85cc1f3c3b1da7e98c3ed Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Tue, 9 Mar 2021 00:05:04 -0500 Subject: [PATCH 16/20] chore: doc update --- .gitignore | 1 + README.md | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0984308..87b6e38 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ mumble-discord-bridge dist bridge .prof +cert.pem \ No newline at end of file diff --git a/README.md b/README.md index 1919f47..c079c0f 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Usage of ./mumble-discord-bridge: -mumble-address string MUMBLE_ADDRESS, mumble server address, example example.com, required -mumble-channel string - MUMBLE_CHANNEL, mumble channel to start in, using '/' to seperate nested channels, optional + MUMBLE_CHANNEL, mumble channel to start in, using '/' to separate nested channels, optional -mumble-disable-text MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false) -mumble-insecure @@ -93,6 +93,15 @@ Discord GID is a unique ID linked to one Discord Server, also called Guild. CID Then you can get the GID by right-clicking your server and selecting Copy-ID. Similarly the CID can be found right clicking the voice channel and selecting Copy ID. +### Generating Mumble Client (Optional) + +Optionally you can specify a client certificate for mumble [Mumble Certificates](https://wiki.mumble.info/wiki/Mumble_Certificates) +If you don't have a client certificate, you can generate one with this command: + +``` bash +openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout cert.pem -out cert.pem -subj "/CN=mumble-discord-bridge" +``` + ### Binary Prebuilt binaries are available. From a8f2574370fde781f85fe5f80c83b96d8059881c Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Sat, 3 Apr 2021 01:32:04 -0400 Subject: [PATCH 17/20] update to discordgo v0.23.2 --- go.mod | 2 +- go.sum | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 35c4812..ad4ffd3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/stieneee/mumble-discord-bridge go 1.15 require ( - github.com/bwmarrin/discordgo v0.22.0 + github.com/bwmarrin/discordgo v0.23.2 github.com/golang/protobuf v1.4.3 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/joho/godotenv v1.3.0 diff --git a/go.sum b/go.sum index bed1128..a7b2e37 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,24 @@ +cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIxDdFM= github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= +github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4= +github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372 h1:tz3KnXWtRZR0RWOfcMNOw+HHezWLQa7vfSOWTtKjchI= github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372/go.mod h1:74z+CYu2/mx4N+mcIS/rsvfAxBPBV9uv8zRAnwyFkdI= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= @@ -34,24 +44,30 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -59,21 +75,27 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210108172913-0df2131ae363 h1:wHn06sgWHMO1VsQ8F+KzDJx/JzqfsNLnc+oEi07qD7s= golang.org/x/sys v0.0.0-20210108172913-0df2131ae363/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -87,6 +109,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa h1:WNU4LYsgD2UHxgKgB36mL6iMAMOvr127alafSlgBbiA= layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa/go.mod h1:AOef7vHz0+v4sWwJnr0jSyHiX/1NgsMoaxl+rEPz/I0= From 177553f3a4ea86f3a046bc8db3040a5f0f83cec3 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Tue, 6 Apr 2021 22:34:38 -0400 Subject: [PATCH 18/20] address static check suggestions --- bridge.go | 2 +- discord-handlers.go | 2 +- discord.go | 16 ++++++++-------- mumble.go | 23 ++++++++++------------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/bridge.go b/bridge.go index 5630adf..571ed0c 100644 --- a/bridge.go +++ b/bridge.go @@ -168,8 +168,8 @@ func (b *BridgeState) startBridge() { go b.DiscordStream.discordSendPCM(ctx, &wg, cancel, toDiscord) // Monitor Mumble + wg.Add(1) go func() { - wg.Add(1) ticker := time.NewTicker(500 * time.Millisecond) for { select { diff --git a/discord-handlers.go b/discord-handlers.go index 5118693..58f3ac2 100644 --- a/discord-handlers.go +++ b/discord-handlers.go @@ -211,7 +211,7 @@ func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.Voi // Remove users that are no longer connected for id := range l.Bridge.DiscordUsers { - if l.Bridge.DiscordUsers[id].seen == false { + if !l.Bridge.DiscordUsers[id].seen { log.Println("User left Discord channel " + l.Bridge.DiscordUsers[id].username) if l.Bridge.Connected && !l.Bridge.BridgeConfig.MumbleDisableText { l.Bridge.MumbleClient.Do(func() { diff --git a/discord.go b/discord.go index 44befca..3fa8518 100644 --- a/discord.go +++ b/discord.go @@ -87,8 +87,8 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, continue } - if dd.Bridge.DiscordVoice.Ready == false || dd.Bridge.DiscordVoice.OpusSend == nil { - if lastReady == true { + if !dd.Bridge.DiscordVoice.Ready || dd.Bridge.DiscordVoice.OpusSend == nil { + if lastReady { OnError(fmt.Sprintf("Discordgo not ready for opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil) readyTimeout = time.AfterFunc(30*time.Second, func() { log.Println("set ready timeout") @@ -97,7 +97,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, lastReady = false } continue - } else if lastReady == false { + } else if !lastReady { fmt.Println("Discordgo ready to send opus packets") lastReady = true readyTimeout.Stop() @@ -123,8 +123,8 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro wg.Add(1) for { - if dd.Bridge.DiscordVoice.Ready == false || dd.Bridge.DiscordVoice.OpusRecv == nil { - if lastReady == true { + if !dd.Bridge.DiscordVoice.Ready || dd.Bridge.DiscordVoice.OpusRecv == nil { + if lastReady { OnError(fmt.Sprintf("Discordgo not to receive opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil) readyTimeout = time.AfterFunc(30*time.Second, func() { log.Println("set ready timeout") @@ -133,7 +133,7 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro lastReady = false } continue - } else if lastReady == false { + } else if !lastReady { fmt.Println("Discordgo ready to receive packets") lastReady = true readyTimeout.Stop() @@ -222,7 +222,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou for i := range dd.fromDiscordMap { if len(dd.fromDiscordMap[i].pcm) > 0 { sendAudio = true - if dd.fromDiscordMap[i].streaming == false { + if !dd.fromDiscordMap[i].streaming { x := dd.fromDiscordMap[i] x.streaming = true dd.fromDiscordMap[i] = x @@ -231,7 +231,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou x1 := (<-dd.fromDiscordMap[i].pcm) internalMixerArr = append(internalMixerArr, x1) } else { - if dd.fromDiscordMap[i].streaming == true { + if dd.fromDiscordMap[i].streaming { x := dd.fromDiscordMap[i] x.streaming = false dd.fromDiscordMap[i] = x diff --git a/mumble.go b/mumble.go index 4f99c70..66a6a4a 100644 --- a/mumble.go +++ b/mumble.go @@ -29,21 +29,18 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { mutex.Unlock() go func() { - // TODO kill go routine on cleanup - log.Println("new mumble audio stream", e.User.Name) - for { - select { - case p := <-e.C: - // log.Println("audio packet", p.Sender.Name, len(p.AudioBuffer)) + name := e.User.Name + log.Println("new mumble audio stream", name) + for p := range e.C { + // log.Println("audio packet", p.Sender.Name, len(p.AudioBuffer)) - // 480 per 10ms - for i := 0; i < len(p.AudioBuffer)/480; i++ { - localMumbleArray <- p.AudioBuffer[480*i : 480*(i+1)] - } + // 480 per 10ms + for i := 0; i < len(p.AudioBuffer)/480; i++ { + localMumbleArray <- p.AudioBuffer[480*i : 480*(i+1)] } } + log.Println("mumble audio stream ended", name) }() - return } func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, toDiscord chan []int16) { @@ -70,7 +67,7 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t for i := 0; i < len(fromMumbleArr); i++ { if len(fromMumbleArr[i]) > 0 { sendAudio = true - if mumbleStreamingArr[i] == false { + if !mumbleStreamingArr[i] { mumbleStreamingArr[i] = true // log.Println("mumble starting", i) } @@ -78,7 +75,7 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t x1 := (<-fromMumbleArr[i]) internalMixerArr = append(internalMixerArr, x1) } else { - if mumbleStreamingArr[i] == true { + if mumbleStreamingArr[i] { mumbleStreamingArr[i] = false // log.Println("mumble stopping", i) } From 480fa533a28b1d79404136312bc5d46e14a98434 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Wed, 7 Apr 2021 01:24:17 -0400 Subject: [PATCH 19/20] fix race data related to reading bridge state --- Makefile | 3 +++ bridge.go | 12 ++++++++++++ discord-handlers.go | 16 +++++++++++++--- discord.go | 14 +++++++++----- main.go | 4 +++- mumble-handlers.go | 5 +++-- 6 files changed, 43 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 221c93c..5d9d6cd 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,9 @@ mumble-discord-bridge: $(GOFILES) dev: $(GOFILES) goreleaser build --skip-validate --rm-dist && sudo ./dist/mumble-discord-bridge_linux_amd64/mumble-discord-bridge +dev-race: $(GOFILES) + go run -race *.go + dev-profile: $(GOFILES) goreleaser build --skip-validate --rm-dist && sudo ./dist/mumble-discord-bridge_linux_amd64/mumble-discord-bridge -cpuprofile cpu.prof diff --git a/bridge.go b/bridge.go index 571ed0c..5703a60 100644 --- a/bridge.go +++ b/bridge.go @@ -35,6 +35,9 @@ type BridgeState struct { // Wait for bridge to exit cleanly WaitExit *sync.WaitGroup + // Bridge State Mutex + BridgeMutex sync.Mutex + // Bridge connection Connected bool @@ -189,7 +192,9 @@ func (b *BridgeState) startBridge() { } }() + b.BridgeMutex.Lock() b.Connected = true + b.BridgeMutex.Unlock() // Hold until cancelled or external die request select { @@ -200,7 +205,10 @@ func (b *BridgeState) startBridge() { cancel() } + b.BridgeMutex.Lock() b.Connected = false + b.BridgeMutex.Unlock() + wg.Wait() log.Println("Terminating Bridge") b.MumbleUsersMutex.Lock() @@ -221,6 +229,7 @@ func (b *BridgeState) discordStatusUpdate() { b.DiscordSession.UpdateListeningStatus("an error pinging mumble") } else { b.MumbleUsersMutex.Lock() + b.BridgeMutex.Lock() b.MumbleUserCount = resp.ConnectedUsers if b.Connected { b.MumbleUserCount = b.MumbleUserCount - 1 @@ -234,6 +243,7 @@ func (b *BridgeState) discordStatusUpdate() { status = fmt.Sprintf("%v users in Mumble\n", b.MumbleUserCount) } } + b.BridgeMutex.Unlock() b.MumbleUsersMutex.Unlock() b.DiscordSession.UpdateListeningStatus(status) } @@ -257,6 +267,7 @@ func (b *BridgeState) AutoBridge() { b.MumbleUsersMutex.Lock() b.DiscordUsersMutex.Lock() + b.BridgeMutex.Lock() if !b.Connected && b.MumbleUserCount > 0 && len(b.DiscordUsers) > 0 { log.Println("users detected in mumble and discord, bridging") @@ -267,6 +278,7 @@ func (b *BridgeState) AutoBridge() { b.BridgeDie <- true } + b.BridgeMutex.Unlock() b.MumbleUsersMutex.Unlock() b.DiscordUsersMutex.Unlock() } diff --git a/discord-handlers.go b/discord-handlers.go index 58f3ac2..e0e3083 100644 --- a/discord-handlers.go +++ b/discord-handlers.go @@ -49,11 +49,13 @@ func (l *DiscordListener) guildCreate(s *discordgo.Session, event *discordgo.Gui l.Bridge.DiscordUsersMutex.Unlock() // If connected to mumble inform users of Discord users + l.Bridge.BridgeMutex.Lock() if l.Bridge.Connected && !l.Bridge.BridgeConfig.MumbleDisableText { l.Bridge.MumbleClient.Do(func() { l.Bridge.MumbleClient.Self.Channel.Send(fmt.Sprintf("%v has joined Discord\n", u.Username), false) }) } + l.Bridge.BridgeMutex.Unlock() } } @@ -85,10 +87,14 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa return } + l.Bridge.BridgeMutex.Lock() + bridgeConnected := l.Bridge.Connected + l.Bridge.BridgeMutex.Unlock() + if strings.HasPrefix(m.Content, prefix+" link") { // Look for the message sender in that guild's current voice states. for _, vs := range g.VoiceStates { - if l.Bridge.Connected { + if bridgeConnected { l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Bridge already running, unlink first") return } @@ -103,7 +109,7 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa if strings.HasPrefix(m.Content, prefix+" unlink") { // Look for the message sender in that guild's current voice states. - if !l.Bridge.Connected { + if !bridgeConnected { l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Bridge is not currently running") return } @@ -118,7 +124,7 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa if strings.HasPrefix(m.Content, prefix+" refresh") { // Look for the message sender in that guild's current voice states. - if !l.Bridge.Connected { + if !bridgeConnected { l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Bridge is not currently running") return } @@ -195,11 +201,13 @@ func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.Voi seen: true, dm: dm, } + l.Bridge.BridgeMutex.Lock() if l.Bridge.Connected && !l.Bridge.BridgeConfig.MumbleDisableText { l.Bridge.MumbleClient.Do(func() { l.Bridge.MumbleClient.Self.Channel.Send(fmt.Sprintf("%v has joined Discord\n", u.Username), false) }) } + l.Bridge.BridgeMutex.Unlock() } else { du := l.Bridge.DiscordUsers[vs.UserID] du.seen = true @@ -213,11 +221,13 @@ func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.Voi for id := range l.Bridge.DiscordUsers { if !l.Bridge.DiscordUsers[id].seen { log.Println("User left Discord channel " + l.Bridge.DiscordUsers[id].username) + l.Bridge.BridgeMutex.Lock() if l.Bridge.Connected && !l.Bridge.BridgeConfig.MumbleDisableText { l.Bridge.MumbleClient.Do(func() { l.Bridge.MumbleClient.Self.Channel.Send(fmt.Sprintf("%v has left Discord channel\n", l.Bridge.DiscordUsers[id].username), false) }) } + l.Bridge.BridgeMutex.Unlock() delete(l.Bridge.DiscordUsers, id) } } diff --git a/discord.go b/discord.go index 3fa8518..4268780 100644 --- a/discord.go +++ b/discord.go @@ -23,9 +23,8 @@ type fromDiscord struct { type DiscordDuplex struct { Bridge *BridgeState - discordMutex sync.Mutex - discordMixerMutex sync.Mutex - fromDiscordMap map[uint32]fromDiscord + discordMutex sync.Mutex + fromDiscordMap map[uint32]fromDiscord } // OnError gets called by dgvoice when an error is encountered. @@ -87,6 +86,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, continue } + dd.Bridge.DiscordVoice.RWMutex.RLock() if !dd.Bridge.DiscordVoice.Ready || dd.Bridge.DiscordVoice.OpusSend == nil { if lastReady { OnError(fmt.Sprintf("Discordgo not ready for opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil) @@ -96,13 +96,15 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, }) lastReady = false } - continue } else if !lastReady { fmt.Println("Discordgo ready to send opus packets") lastReady = true readyTimeout.Stop() + } else { + dd.Bridge.DiscordVoice.OpusSend <- opus } - dd.Bridge.DiscordVoice.OpusSend <- opus + dd.Bridge.DiscordVoice.RWMutex.RUnlock() + } else { if streaming { dd.Bridge.DiscordVoice.Speaking(false) @@ -123,6 +125,7 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro wg.Add(1) for { + dd.Bridge.DiscordVoice.RWMutex.RLock() if !dd.Bridge.DiscordVoice.Ready || dd.Bridge.DiscordVoice.OpusRecv == nil { if lastReady { OnError(fmt.Sprintf("Discordgo not to receive opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil) @@ -138,6 +141,7 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro lastReady = true readyTimeout.Stop() } + dd.Bridge.DiscordVoice.RWMutex.RUnlock() var ok bool var p *discordgo.Packet diff --git a/main.go b/main.go index e06bef2..00e720b 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,7 @@ func main() { mumblePassword := flag.String("mumble-password", lookupEnvOrString("MUMBLE_PASSWORD", ""), "MUMBLE_PASSWORD, mumble password, optional") mumbleInsecure := flag.Bool("mumble-insecure", lookupEnvOrBool("MUMBLE_INSECURE", false), " MUMBLE_INSECURE, mumble insecure, optional") mumbleCertificate := flag.String("mumble-certificate", lookupEnvOrString("MUMBLE_CERTIFICATE", ""), "MUMBLE_CERTIFICATE, client certificate to use when connecting to the Mumble server") - mumbleChannel := flag.String("mumble-channel", lookupEnvOrString("MUMBLE_CHANNEL", ""), "MUMBLE_CHANNEL, mumble channel to start in, using '/' to seperate nested channels, optional") + mumbleChannel := flag.String("mumble-channel", lookupEnvOrString("MUMBLE_CHANNEL", ""), "MUMBLE_CHANNEL, mumble channel to start in, using '/' to separate nested channels, optional") mumbleDisableText := flag.Bool("mumble-disable-text", lookupEnvOrBool("MUMBLE_DISABLE_TEXT", false), "MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false)") discordToken := flag.String("discord-token", lookupEnvOrString("DISCORD_TOKEN", ""), "DISCORD_TOKEN, discord bot token, required") discordGID := flag.String("discord-gid", lookupEnvOrString("DISCORD_GID", ""), "DISCORD_GID, discord gid, required") @@ -197,9 +197,11 @@ func main() { log.Println("OS Signal. Bot shutting down") // Wait or the bridge to exit cleanly + Bridge.BridgeMutex.Lock() if Bridge.Connected { //TODO BridgeDie occasionally panics on send to closed channel Bridge.BridgeDie <- true Bridge.WaitExit.Wait() } + Bridge.BridgeMutex.Unlock() } diff --git a/mumble-handlers.go b/mumble-handlers.go index 6350c68..3dc58df 100644 --- a/mumble-handlers.go +++ b/mumble-handlers.go @@ -43,22 +43,23 @@ func (l *MumbleListener) mumbleUserChange(e *gumble.UserChangeEvent) { e.User.Send("Mumble-Discord-Bridge v" + version) // Tell the user who is connected to discord + l.Bridge.DiscordUsersMutex.Lock() if len(l.Bridge.DiscordUsers) == 0 { e.User.Send("No users connected to Discord") } else { s := "Connected to Discord: " arr := []string{} - l.Bridge.DiscordUsersMutex.Lock() for u := range l.Bridge.DiscordUsers { arr = append(arr, l.Bridge.DiscordUsers[u].username) } s = s + strings.Join(arr[:], ",") - l.Bridge.DiscordUsersMutex.Unlock() e.User.Send(s) } + l.Bridge.DiscordUsersMutex.Unlock() + } // Send discord a notice From a18df282133d2ce7432ae84fa79eda2bfbb34b08 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Sat, 10 Apr 2021 16:19:02 -0400 Subject: [PATCH 20/20] chore: doc update for mumble serve issue closes #17 --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f0797f4..23a072b 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,9 @@ Audio leveling from Discord needs to be improved. Delays in connecting to Mumble (such as from external authentication plugins) may result in extra error messages on initial connection. +There is an issue seen with Mumble-Server (murmur) 1.3.0 in which the bridge will loose the ability to send messages client after prolonged periods of connectivity. +This issue has been appears to be resolved by murmur 1.3.4. + ## License Distributed under the MIT License. See LICENSE for more information.