diff --git a/discord.go b/discord.go index 8988850..820a223 100644 --- a/discord.go +++ b/discord.go @@ -18,10 +18,19 @@ type fromDiscord struct { streaming bool } +type discordUser struct { + username string + seen bool + dm *discordgo.Channel +} + var discordMutex sync.Mutex var discordMixerMutex sync.Mutex var fromDiscordMap = make(map[uint32]fromDiscord) +var discordUsersMutex sync.Mutex +var discordUsers = make(map[string]discordUser) // id is the key + // OnError gets called by dgvoice when an error is encountered. // By default logs to STDERR var OnError = func(str string, err error) { @@ -227,3 +236,96 @@ func fromDiscordMixer(toMumble chan<- gumble.AudioBuffer) { } } } + +// This function acts a filter from the interall discordgo state the local state +// This function +func discordMemberWatcher(d *discordgo.Session, m *gumble.Client) { + ticker := time.NewTicker(250 * time.Millisecond) + + g, err := d.State.Guild(*discordGID) + if err != nil { + log.Println("error finding guild") + panic(err) + } + + // Watch Discordgo internal state for member changes in the channel + for { + <-ticker.C + + discordUsersMutex.Lock() + + // start := time.Now() + + // Set all members to false + for u := range discordUsers { + du := discordUsers[u] + du.seen = false + discordUsers[u] = du + } + + // Sync the channel voice states to the local discordUsersMap + for _, vs := range g.VoiceStates { + if vs.ChannelID == *discordCID { + if d.State.User.ID == vs.UserID { + continue + } + + if _, ok := discordUsers[vs.UserID]; !ok { + + u, err := d.User(vs.UserID) + if err != nil { + log.Println("error looking up username") + continue + } + + println("User joined Discord " + u.Username) + dm, err := d.UserChannelCreate(u.ID) + if err != nil { + log.Panicln("Error creating private channel for", u.Username) + } + discordUsers[vs.UserID] = discordUser{ + username: u.Username, + seen: true, + dm: dm, + } + m.Do(func() { + m.Self.Channel.Send(fmt.Sprintf("%v has joined Discord\n", u.Username), false) + }) + } else { + du := discordUsers[vs.UserID] + du.seen = true + discordUsers[vs.UserID] = du + } + + } + } + + // Remove users that are no longer connected + for id := range discordUsers { + if discordUsers[id].seen == false { + println("User left Discord channel " + discordUsers[id].username) + m.Do(func() { + m.Self.Channel.Send(fmt.Sprintf("%v has left Discord channel\n", discordUsers[id].username), false) + }) + delete(discordUsers, id) + } + } + + discordUsersMutex.Unlock() + + // elapsed := time.Since(start) + // log.Printf("Discord user sync took %s", elapsed) + } +} + +func discordSendMessageAll(d *discordgo.Session, msg string) { + discordUsersMutex.Lock() + for id := range discordUsers { + du := discordUsers[id] + if du.dm != nil { + log.Println("Sensing msg to ") + d.ChannelMessageSend(du.dm.ID, msg) + } + } + discordUsersMutex.Unlock() +} diff --git a/main.go b/main.go index 06880c5..b343f09 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,11 @@ var ( date string ) +var ( + discordGID *string + discordCID *string +) + func lookupEnvOrString(key string, defaultVal string) string { if val, ok := os.LookupEnv(key); ok { return val @@ -71,13 +76,13 @@ func main() { mumbleAddr := flag.String("mumble-address", lookupEnvOrString("MUMBLE_ADDRESS", ""), "MUMBLE_ADDRESS, mumble server address, example example.com") mumblePort := flag.Int("mumble-port", lookupEnvOrInt("MUMBLE_PORT", 64738), "MUMBLE_PORT mumble port") - mumbleUsername := flag.String("mumble-username", lookupEnvOrString("MUMBLE_USERNAME", "discord-bridge"), "MUMBLE_USERNAME, mumble username") + mumbleUsername := flag.String("mumble-username", lookupEnvOrString("MUMBLE_USERNAME", "discord"), "MUMBLE_USERNAME, mumble username") mumblePassword := flag.String("mumble-password", lookupEnvOrString("MUMBLE_PASSWORD", ""), "MUMBLE_PASSWORD, mumble password, optional") mumbleInsecure := flag.Bool("mumble-insecure", lookupEnvOrBool("MUMBLE_INSECURE", false), "mumble insecure, env alt MUMBLE_INSECURE") discordToken := flag.String("discord-token", lookupEnvOrString("DISCORD_TOKEN", ""), "DISCORD_TOKEN, discord bot token") - discordGID := flag.String("discord-gid", lookupEnvOrString("DISCORD_GID", ""), "DISCORD_GID, discord gid") - discordCID := flag.String("discord-cid", lookupEnvOrString("DISCORD_CID", ""), "DISCORD_CID, discord cid") + discordGID = flag.String("discord-gid", lookupEnvOrString("DISCORD_GID", ""), "DISCORD_GID, discord gid") + discordCID = flag.String("discord-cid", lookupEnvOrString("DISCORD_CID", ""), "DISCORD_CID, discord cid") flag.Parse() log.Printf("app.config %v\n", getConfig(flag.CommandLine)) @@ -116,7 +121,11 @@ func main() { } // Open Websocket + discord.ShouldReconnectOnError = true discord.LogLevel = 1 + discord.StateEnabled = true + discord.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged) + err = discord.Open() if err != nil { log.Println(err) @@ -134,8 +143,6 @@ func main() { defer dgv.Speaking(false) defer dgv.Close() - discord.ShouldReconnectOnError = true - // MUMBLE Setup config := gumble.NewConfig() @@ -144,7 +151,9 @@ func main() { config.AudioInterval = time.Millisecond * 10 m := MumbleDuplex{} - ml := MumbleEventListener{} + ml := MumbleEventListener{ + d: discord, + } var tlsConfig tls.Config if *mumbleInsecure { @@ -167,6 +176,8 @@ func main() { log.Println("Mumble Connected") + // Initial User States + // Start Passing Between // Mumble go m.fromMumbleMixer(toDiscord) @@ -176,6 +187,7 @@ func main() { go discordReceivePCM(dgv, die) go fromDiscordMixer(toMumble) go discordSendPCM(dgv, toDiscord, die) + go discordMemberWatcher(discord, mumble) // Wait for Exit Signal c := make(chan os.Signal) diff --git a/mumble-event.go b/mumble-event.go index 4901abe..c1ebed2 100644 --- a/mumble-event.go +++ b/mumble-event.go @@ -2,13 +2,17 @@ package main import ( "fmt" + "strings" + "github.com/bwmarrin/discordgo" "layeh.com/gumble/gumble" _ "layeh.com/gumble/opus" ) // MumbleEventListener - Bridge Event Handler -type MumbleEventListener struct{} +type MumbleEventListener struct { + d *discordgo.Session +} // OnConnect - event handler func (ml MumbleEventListener) OnConnect(e *gumble.ConnectEvent) { @@ -28,6 +32,33 @@ func (ml MumbleEventListener) OnTextMessage(e *gumble.TextMessageEvent) { // OnUserChange - event handler func (ml MumbleEventListener) OnUserChange(e *gumble.UserChangeEvent) { fmt.Println("OnUserChange", e.User.Name, e) + if e.Type.Has(gumble.UserChangeConnected) { + e.User.Send("Mumble-Discord-Bridge v" + version) + + // Tell the user who is connected to discord + if len(discordUsers) == 0 { + e.User.Send("No users connected to Discord") + } else { + s := "Users connected to Discord: " + + arr := []string{} + discordUsersMutex.Lock() + for u := range discordUsers { + arr = append(arr, u) + } + + s = s + strings.Join(arr[:], ",") + + discordUsersMutex.Unlock() + e.User.Send(s) + } + + // Send discord a notice + discordSendMessageAll(ml.d, e.User.Name+" has joined mumble") + } + if e.Type.Has(gumble.UserChangeDisconnected) { + discordSendMessageAll(ml.d, e.User.Name+" has left mumble") + } } // OnChannelChange - event handler