From dbe46c87fc30fc6652ee989bf2d034808901b5f9 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Thu, 13 May 2021 02:25:42 -0400 Subject: [PATCH] readd missing files --- .gitignore | 1 - cmd/mumble-discord-bridge/config.go | 51 ++++++ cmd/mumble-discord-bridge/main.go | 239 ++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 cmd/mumble-discord-bridge/config.go create mode 100644 cmd/mumble-discord-bridge/main.go diff --git a/.gitignore b/.gitignore index 380ea08..03432ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .env main -mumble-discord-bridge dist *.prof *.out diff --git a/cmd/mumble-discord-bridge/config.go b/cmd/mumble-discord-bridge/config.go new file mode 100644 index 0000000..344a7ce --- /dev/null +++ b/cmd/mumble-discord-bridge/config.go @@ -0,0 +1,51 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "strconv" + "strings" +) + +//BridgeConfig holds configuration information set at startup +//It should not change during runtime + +func lookupEnvOrString(key string, defaultVal string) string { + if val, ok := os.LookupEnv(key); ok { + return strings.TrimSpace(val) + } + return defaultVal +} + +func lookupEnvOrInt(key string, defaultVal int) int { + if val, ok := os.LookupEnv(key); ok { + v, err := strconv.Atoi(val) + if err != nil { + log.Fatalf("LookupEnvOrInt[%s]: %v", key, err) + } + return v + } + return defaultVal +} + +func lookupEnvOrBool(key string, defaultVal bool) bool { + if val, ok := os.LookupEnv(key); ok { + v, err := strconv.ParseBool(val) + if err != nil { + log.Fatalf("LookupEnvOrInt[%s]: %v", key, err) + } + return v + } + return defaultVal +} + +func getConfig(fs *flag.FlagSet) []string { + cfg := make([]string, 0, 10) + fs.VisitAll(func(f *flag.Flag) { + cfg = append(cfg, fmt.Sprintf("%s:%q", f.Name, f.Value.String())) + }) + + return cfg +} diff --git a/cmd/mumble-discord-bridge/main.go b/cmd/mumble-discord-bridge/main.go new file mode 100644 index 0000000..4e66f16 --- /dev/null +++ b/cmd/mumble-discord-bridge/main.go @@ -0,0 +1,239 @@ +package main + +import ( + "flag" + "fmt" + "log" + "math" + "os" + "os/signal" + "runtime/pprof" + "strconv" + "strings" + "syscall" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/joho/godotenv" + "github.com/stieneee/gumble/gumble" + "github.com/stieneee/gumble/gumbleutil" + "github.com/stieneee/mumble-discord-bridge/internal/bridge" +) + +var ( + // Build vars + version string + commit string + date string +) + +func main() { + var err error + + fmt.Println("Mumble-Discord-Bridge") + fmt.Println("v" + version + " " + commit + " " + date) + + godotenv.Load() + + mumbleAddr := flag.String("mumble-address", lookupEnvOrString("MUMBLE_ADDRESS", ""), "MUMBLE_ADDRESS, mumble server address, example example.com, required") + mumblePort := flag.Int("mumble-port", lookupEnvOrInt("MUMBLE_PORT", 64738), "MUMBLE_PORT, mumble port, (default 64738)") + 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 separate nested channels, optional") + mumbleSendBuffer := flag.Int("to-mumble-buffer", lookupEnvOrInt("TO_MUMBLE_BUFFER", 50), "TO_MUMBLE_BUFFER, Jitter buffer from Discord to Mumble to absorb timing issues related to network, OS and hardware quality. (Increments of 10ms)") + 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") + discordCID := flag.String("discord-cid", lookupEnvOrString("DISCORD_CID", ""), "DISCORD_CID, discord cid, required") + discordSendBuffer := flag.Int("to-discord-buffer", lookupEnvOrInt("TO_DISCORD_BUFFER", 50), "TO_DISCORD_BUFFER, Jitter buffer from Mumble to Discord to absorb timing issues related to network, OS and hardware quality. (Increments of 10ms)") + discordCommand := flag.String("discord-command", lookupEnvOrString("DISCORD_COMMAND", "mumble-discord"), "DISCORD_COMMAND, Discord command string, env alt DISCORD_COMMAND, optional, (defaults mumble-discord)") + 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`") + + flag.Parse() + log.Printf("app.config %v\n", getConfig(flag.CommandLine)) + + if *mumbleAddr == "" { + log.Fatalln("missing mumble address") + } + if *mumbleUsername == "" { + log.Fatalln("missing mumble username") + } + + if *discordToken == "" { + log.Fatalln("missing discord bot token") + } + if *discordGID == "" { + log.Fatalln("missing discord gid") + } + if *discordCID == "" { + log.Fatalln("missing discord cid") + } + if *mode == "" { + log.Fatalln("missing mode set") + } + if *nice { + err := syscall.Setpriority(syscall.PRIO_PROCESS, os.Getpid(), -5) + if err != nil { + log.Println("Unable to set priority. ", err) + } + } + + // Optional CPU Profiling + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal("could not create CPU profile: ", err) + } + defer f.Close() // error handling omitted for example + if err := pprof.StartCPUProfile(f); err != nil { + log.Fatal("could not start CPU profile: ", err) + } + defer pprof.StopCPUProfile() + } + + // Buffer Math + if *discordSendBuffer < 10 { + *discordSendBuffer = 10 + } + + if *mumbleSendBuffer < 10 { + *mumbleSendBuffer = 10 + } + + var discordStartStreamingCount int = int(math.Round(float64(*discordSendBuffer) / 10.0)) + log.Println("To Discord Jitter Buffer: ", discordStartStreamingCount*10, " ms") + + var mumbleStartStreamCount int = int(math.Round(float64(*mumbleSendBuffer) / 10.0)) + log.Println("To Mumble Jitter Buffer: ", mumbleStartStreamCount*10, " ms") + + // BRIDGE SETUP + + Bridge := &bridge.BridgeState{ + BridgeConfig: &bridge.BridgeConfig{ + // MumbleConfig: config, + MumbleAddr: *mumbleAddr + ":" + strconv.Itoa(*mumblePort), + MumbleInsecure: *mumbleInsecure, + MumbleCertificate: *mumbleCertificate, + MumbleChannel: strings.Split(*mumbleChannel, "/"), + MumbleStartStreamCount: mumbleStartStreamCount, + MumbleDisableText: *mumbleDisableText, + Command: *discordCommand, + GID: *discordGID, + CID: *discordCID, + DiscordStartStreamingCount: discordStartStreamingCount, + DiscordDisableText: *discordDisableText, + Version: version, + }, + Connected: false, + DiscordUsers: make(map[string]bridge.DiscordUser), + MumbleUsers: make(map[string]bool), + } + + // MUMBLE SETUP + Bridge.BridgeConfig.MumbleConfig = gumble.NewConfig() + Bridge.BridgeConfig.MumbleConfig.Username = *mumbleUsername + Bridge.BridgeConfig.MumbleConfig.Password = *mumblePassword + Bridge.BridgeConfig.MumbleConfig.AudioInterval = time.Millisecond * 10 + + Bridge.MumbleListener = &bridge.MumbleListener{ + Bridge: Bridge, + } + + Bridge.BridgeConfig.MumbleConfig.Attach(gumbleutil.Listener{ + Connect: Bridge.MumbleListener.MumbleConnect, + UserChange: Bridge.MumbleListener.MumbleUserChange, + }) + + // DISCORD SETUP + + //Connect to discord + Bridge.DiscordSession, err = discordgo.New("Bot " + *discordToken) + if err != nil { + log.Println(err) + return + } + + Bridge.DiscordSession.LogLevel = *debug + Bridge.DiscordSession.StateEnabled = true + Bridge.DiscordSession.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged) + Bridge.DiscordSession.ShouldReconnectOnError = true + // register handlers + Bridge.DiscordListener = &bridge.DiscordListener{ + Bridge: Bridge, + } + Bridge.DiscordSession.AddHandler(Bridge.DiscordListener.MessageCreate) + Bridge.DiscordSession.AddHandler(Bridge.DiscordListener.GuildCreate) + Bridge.DiscordSession.AddHandler(Bridge.DiscordListener.VoiceUpdate) + + // Open Discord websocket + err = Bridge.DiscordSession.Open() + if err != nil { + log.Println(err) + return + } + defer Bridge.DiscordSession.Close() + + log.Println("Discord Bot Connected") + log.Printf("Discord bot looking for command !%v", *discordCommand) + + switch *mode { + case "auto": + log.Println("bridge starting in automatic mode") + Bridge.AutoChanDie = make(chan bool) + Bridge.Mode = bridge.BridgeModeAuto + Bridge.DiscordChannelID = Bridge.BridgeConfig.CID + go Bridge.AutoBridge() + case "manual": + log.Println("bridge starting in manual mode") + Bridge.Mode = bridge.BridgeModeManual + case "constant": + log.Println("bridge starting in constant mode") + Bridge.Mode = bridge.BridgeModeConstant + Bridge.DiscordChannelID = Bridge.BridgeConfig.CID + go func() { + defer func() { + if r := recover(); r != nil { + fmt.Println("Bridge paniced", r) + } + }() + for { + Bridge.StartBridge() + log.Println("Bridge died") + time.Sleep(5 * time.Second) + log.Println("Restarting") + } + }() + default: + Bridge.DiscordSession.Close() + log.Fatalln("invalid bridge mode set") + } + + go Bridge.DiscordStatusUpdate() + + // Shutdown on OS signal + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + <-sc + + log.Println("OS Signal. Bot shutting down") + + time.AfterFunc(30*time.Second, func() { + os.Exit(99) + }) + + // 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() +}