Merge branch 'main' of github.com:stryan/mumble-discord-bridge

This commit is contained in:
stryan 2021-04-15 11:10:20 -04:00
commit 812733d669
12 changed files with 180 additions and 53 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ mumble-discord-bridge
dist
bridge
.prof
cert.pem

View File

@ -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

View File

@ -27,11 +27,13 @@ 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 separate nested channels, optional
-mumble-disable-text
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
@ -40,6 +42,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:
@ -74,7 +78,31 @@ 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.
### 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
@ -141,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.

View File

@ -6,6 +6,7 @@ import (
"fmt"
"log"
"net"
"os"
"strconv"
"sync"
"time"
@ -34,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
@ -70,6 +74,9 @@ type BridgeState struct {
// Mumble Duplex and Event Listener
MumbleStream *MumbleDuplex
MumbleListener *MumbleListener
// Discord Voice channel to join
DiscordChannelID string
}
// startBridge established the voice connection
@ -90,7 +97,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 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)
b.DiscordVoice.Disconnect()
@ -111,6 +123,16 @@ func (b *BridgeState) startBridge() {
tlsConfig.InsecureSkipVerify = true
}
if b.BridgeConfig.MumbleCertificate != "" {
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 {
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)
@ -149,6 +171,7 @@ 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)
@ -170,7 +193,9 @@ func (b *BridgeState) startBridge() {
}
}()
b.BridgeMutex.Lock()
b.Connected = true
b.BridgeMutex.Unlock()
// Hold until cancelled or external die request
select {
@ -181,7 +206,10 @@ func (b *BridgeState) startBridge() {
cancel()
}
b.BridgeMutex.Lock()
b.Connected = false
b.BridgeMutex.Unlock()
wg.Wait()
log.Println("Terminating Bridge")
b.MumbleUsersMutex.Lock()
@ -202,6 +230,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
@ -215,6 +244,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)
}
@ -238,6 +268,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")
@ -248,6 +279,7 @@ func (b *BridgeState) AutoBridge() {
b.BridgeDie <- true
}
b.BridgeMutex.Unlock()
b.MumbleUsersMutex.Unlock()
b.DiscordUsersMutex.Unlock()
}

View File

@ -24,7 +24,8 @@ type BridgeConfig struct {
MumbleConfig *gumble.Config
MumbleAddr string
MumbleInsecure bool
MumbleChannel string
MumbleCertificate string
MumbleChannel []string
MumbleDisableText bool
Command string
GID string

View File

@ -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
@ -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()
}
}
@ -61,10 +63,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,11 +81,26 @@ 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
}
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 bridgeConnected {
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
go l.Bridge.startBridge()
return
}
@ -96,8 +109,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 !bridgeConnected {
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
@ -107,6 +124,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 !bridgeConnected {
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)
@ -122,10 +143,14 @@ 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.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
}
@ -152,7 +177,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
@ -176,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
@ -192,13 +219,15 @@ 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)
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)
}
}

View File

@ -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,8 +86,9 @@ 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 {
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)
readyTimeout = time.AfterFunc(30*time.Second, func() {
log.Println("set ready timeout")
@ -96,13 +96,15 @@ 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()
} 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,8 +125,9 @@ 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 {
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)
readyTimeout = time.AfterFunc(30*time.Second, func() {
log.Println("set ready timeout")
@ -133,11 +136,12 @@ 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()
}
dd.Bridge.DiscordVoice.RWMutex.RUnlock()
var ok bool
var p *discordgo.Packet
@ -222,7 +226,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 +235,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

2
go.mod
View File

@ -3,7 +3,7 @@ module git.saintnet.tech/stryan/yammerbot
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

23
go.sum
View File

@ -1,13 +1,23 @@
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/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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -30,42 +40,54 @@ 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/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=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/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=
@ -78,6 +100,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=

19
main.go
View File

@ -8,6 +8,7 @@ import (
"os/signal"
"runtime/pprof"
"strconv"
"strings"
"syscall"
"time"
@ -38,7 +39,8 @@ 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")
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 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")
@ -47,6 +49,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`")
@ -99,7 +102,8 @@ func main() {
// MumbleConfig: config,
MumbleAddr: *mumbleAddr + ":" + strconv.Itoa(*mumblePort),
MumbleInsecure: *mumbleInsecure,
MumbleChannel: *mumbleChannel,
MumbleCertificate: *mumbleCertificate,
MumbleChannel: strings.Split(*mumbleChannel, "/"),
MumbleDisableText: *mumbleDisableText,
Command: *discordCommand,
GID: *discordGID,
@ -135,7 +139,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
@ -163,6 +167,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")
@ -170,6 +175,7 @@ func main() {
case "constant":
log.Println("bridge starting in constant mode")
Bridge.Mode = bridgeModeConstant
Bridge.DiscordChannelID = Bridge.BridgeConfig.CID
go func() {
for {
Bridge.startBridge()
@ -188,13 +194,14 @@ func main() {
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
<-sc
// Signal the bridge to exit cleanly
Bridge.BridgeDie <- true
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()
}

View File

@ -13,12 +13,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)
}
}
@ -45,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

View File

@ -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)
}