2020-10-29 02:21:07 -04:00
package main
import (
"flag"
2021-01-19 01:06:08 -05:00
"fmt"
2020-10-29 02:21:07 -04:00
"log"
2021-04-10 15:00:13 -04:00
"math"
2020-10-29 02:21:07 -04:00
"os"
"os/signal"
2021-01-16 13:40:12 -05:00
"runtime/pprof"
2020-10-29 02:21:07 -04:00
"strconv"
2021-02-08 13:16:09 -05:00
"strings"
2021-01-04 21:23:52 -05:00
"syscall"
2020-10-29 02:21:07 -04:00
"time"
"github.com/bwmarrin/discordgo"
"github.com/joho/godotenv"
"layeh.com/gumble/gumble"
2021-01-06 19:12:56 -05:00
"layeh.com/gumble/gumbleutil"
2020-10-29 02:21:07 -04:00
_ "layeh.com/gumble/opus"
)
2021-01-09 18:18:31 -05:00
var (
// Build vars
version string
commit string
date string
)
2020-10-29 02:21:07 -04:00
func main ( ) {
2021-01-19 01:06:08 -05:00
var err error
fmt . Println ( "Mumble-Discord-Bridge" )
fmt . Println ( "v" + version + " " + commit + " " + date )
2021-01-09 18:18:31 -05:00
2020-10-29 02:21:07 -04:00
godotenv . Load ( )
2021-01-19 01:06:08 -05:00
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)" )
2020-10-29 02:21:07 -04:00
mumblePassword := flag . String ( "mumble-password" , lookupEnvOrString ( "MUMBLE_PASSWORD" , "" ) , "MUMBLE_PASSWORD, mumble password, optional" )
2021-01-19 01:06:08 -05:00
mumbleInsecure := flag . Bool ( "mumble-insecure" , lookupEnvOrBool ( "MUMBLE_INSECURE" , false ) , " MUMBLE_INSECURE, mumble insecure, optional" )
2021-02-25 16:22:06 -05:00
mumbleCertificate := flag . String ( "mumble-certificate" , lookupEnvOrString ( "MUMBLE_CERTIFICATE" , "" ) , "MUMBLE_CERTIFICATE, client certificate to use when connecting to the Mumble server" )
2021-04-07 01:24:17 -04:00
mumbleChannel := flag . String ( "mumble-channel" , lookupEnvOrString ( "MUMBLE_CHANNEL" , "" ) , "MUMBLE_CHANNEL, mumble channel to start in, using '/' to separate nested channels, optional" )
2021-04-19 23:25:45 -04:00
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)" )
2021-01-19 01:06:08 -05:00
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" )
2021-04-19 23:25:45 -04:00
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)" )
2021-01-19 01:06:08 -05:00
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)" )
2021-02-08 13:25:48 -05:00
debug := flag . Int ( "debug-level" , lookupEnvOrInt ( "DEBUG" , 1 ) , "DEBUG_LEVEL, Discord debug level, optional, (default 1)" )
2021-01-16 14:06:58 -05:00
cpuprofile := flag . String ( "cpuprofile" , "" , "write cpu profile to `file`" )
2020-10-29 02:21:07 -04:00
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" )
}
2021-01-05 12:16:51 -05:00
if * mode == "" {
log . Fatalln ( "missing mode set" )
2021-01-04 21:23:52 -05:00
}
2021-01-12 10:40:12 -05:00
if * nice {
err := syscall . Setpriority ( syscall . PRIO_PROCESS , os . Getpid ( ) , - 5 )
if err != nil {
log . Println ( "Unable to set priority. " , err )
}
2020-10-29 02:21:07 -04:00
}
2021-01-19 01:06:08 -05:00
// Optional CPU Profiling
2021-01-16 13:40:12 -05:00
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 ( )
2020-10-29 02:21:07 -04:00
}
2021-04-10 15:00:13 -04:00
// Buffer Math
if * discordSendBuffer < 10 {
* discordSendBuffer = 10
}
2021-04-19 23:25:45 -04:00
if * mumbleSendBuffer < 10 {
* mumbleSendBuffer = 10
}
2021-04-10 15:00:13 -04:00
var discordStartStreamingCount int = int ( math . Round ( float64 ( * discordSendBuffer ) / 10.0 ) )
2021-04-19 23:25:45 -04:00
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" )
2021-04-10 15:00:13 -04:00
2021-01-19 01:06:08 -05:00
// BRIDGE SETUP
Bridge := & BridgeState {
BridgeConfig : & BridgeConfig {
// MumbleConfig: config,
2021-04-10 15:00:13 -04:00
MumbleAddr : * mumbleAddr + ":" + strconv . Itoa ( * mumblePort ) ,
MumbleInsecure : * mumbleInsecure ,
MumbleCertificate : * mumbleCertificate ,
MumbleChannel : strings . Split ( * mumbleChannel , "/" ) ,
2021-04-19 23:25:45 -04:00
mumbleStartStreamCount : mumbleStartStreamCount ,
2021-04-10 15:00:13 -04:00
MumbleDisableText : * mumbleDisableText ,
Command : * discordCommand ,
GID : * discordGID ,
CID : * discordCID ,
DiscordStartStreamingCount : discordStartStreamingCount ,
DiscordDisableText : * discordDisableText ,
2021-01-19 01:06:08 -05:00
} ,
Connected : false ,
DiscordUsers : make ( map [ string ] discordUser ) ,
MumbleUsers : make ( map [ string ] bool ) ,
}
// MUMBLE SETUP
2021-02-01 16:03:38 -05:00
Bridge . BridgeConfig . MumbleConfig = gumble . NewConfig ( )
Bridge . BridgeConfig . MumbleConfig . Username = * mumbleUsername
Bridge . BridgeConfig . MumbleConfig . Password = * mumblePassword
Bridge . BridgeConfig . MumbleConfig . AudioInterval = time . Millisecond * 10
2021-01-19 01:06:08 -05:00
Bridge . MumbleListener = & MumbleListener {
Bridge : Bridge ,
}
2021-02-01 16:03:38 -05:00
Bridge . BridgeConfig . MumbleConfig . Attach ( gumbleutil . Listener {
2021-01-19 01:06:08 -05:00
Connect : Bridge . MumbleListener . mumbleConnect ,
UserChange : Bridge . MumbleListener . mumbleUserChange ,
} )
// DISCORD SETUP
2021-01-05 20:21:08 -05:00
//Connect to discord
2021-01-19 01:06:08 -05:00
Bridge . DiscordSession , err = discordgo . New ( "Bot " + * discordToken )
2020-10-29 02:21:07 -04:00
if err != nil {
log . Println ( err )
return
}
2020-11-04 01:12:43 -05:00
2021-02-08 13:25:48 -05:00
Bridge . DiscordSession . LogLevel = * debug
2021-01-19 01:06:08 -05:00
Bridge . DiscordSession . StateEnabled = true
Bridge . DiscordSession . Identify . Intents = discordgo . MakeIntent ( discordgo . IntentsAllWithoutPrivileged )
Bridge . DiscordSession . ShouldReconnectOnError = true
2021-01-05 20:21:08 -05:00
// register handlers
2021-01-19 01:06:08 -05:00
Bridge . DiscordListener = & 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 ( )
2020-11-04 01:12:43 -05:00
if err != nil {
log . Println ( err )
return
}
2021-01-19 01:06:08 -05:00
defer Bridge . DiscordSession . Close ( )
2020-11-04 01:12:43 -05:00
2021-01-05 20:21:08 -05:00
log . Println ( "Discord Bot Connected" )
log . Printf ( "Discord bot looking for command !%v" , * discordCommand )
2021-01-05 12:16:51 -05:00
switch * mode {
case "auto" :
log . Println ( "bridge starting in automatic mode" )
2021-01-19 01:06:08 -05:00
Bridge . AutoChanDie = make ( chan bool )
2021-01-08 14:38:21 -05:00
Bridge . Mode = bridgeModeAuto
2021-02-07 15:44:20 -05:00
Bridge . DiscordChannelID = Bridge . BridgeConfig . CID
2021-01-19 01:06:08 -05:00
go Bridge . AutoBridge ( )
2021-01-05 12:16:51 -05:00
case "manual" :
log . Println ( "bridge starting in manual mode" )
2021-01-08 14:38:21 -05:00
Bridge . Mode = bridgeModeManual
2021-01-05 12:16:51 -05:00
case "constant" :
log . Println ( "bridge starting in constant mode" )
2021-01-08 14:38:21 -05:00
Bridge . Mode = bridgeModeConstant
2021-02-07 15:44:20 -05:00
Bridge . DiscordChannelID = Bridge . BridgeConfig . CID
2021-02-01 16:03:38 -05:00
go func ( ) {
for {
2021-02-07 15:44:20 -05:00
Bridge . startBridge ( )
2021-02-01 16:03:38 -05:00
log . Println ( "Bridge died. Restarting" )
}
} ( )
2021-01-05 12:16:51 -05:00
default :
2021-01-19 01:06:08 -05:00
Bridge . DiscordSession . Close ( )
2021-01-05 12:16:51 -05:00
log . Fatalln ( "invalid bridge mode set" )
2020-10-29 02:21:07 -04:00
}
2021-01-05 12:16:51 -05:00
2021-01-19 01:06:08 -05:00
go Bridge . discordStatusUpdate ( )
2021-02-01 16:03:38 -05:00
// Shutdown on OS signal
2020-12-29 15:14:19 -05:00
sc := make ( chan os . Signal , 1 )
signal . Notify ( sc , syscall . SIGINT , syscall . SIGTERM , os . Interrupt , os . Kill )
<- sc
2021-02-01 16:03:38 -05:00
log . Println ( "OS Signal. Bot shutting down" )
// Wait or the bridge to exit cleanly
2021-04-07 01:24:17 -04:00
Bridge . BridgeMutex . Lock ( )
2021-02-01 16:03:38 -05:00
if Bridge . Connected {
2021-02-11 14:37:34 -05:00
//TODO BridgeDie occasionally panics on send to closed channel
Bridge . BridgeDie <- true
2021-02-01 16:03:38 -05:00
Bridge . WaitExit . Wait ( )
}
2021-04-07 01:24:17 -04:00
Bridge . BridgeMutex . Unlock ( )
2020-10-29 02:21:07 -04:00
}