This repository has been archived on 2019-06-23. You can view files and clone it, but cannot push or open issues or pull requests.
mumbledj/main.go

261 lines
7.7 KiB
Go
Raw Normal View History

2014-12-09 06:45:22 +01:00
/*
* MumbleDJ
* By Matthieu Grieger
* main.go
2015-01-18 23:44:40 +01:00
* Copyright (c) 2014, 2015 Matthieu Grieger (MIT License)
2014-12-09 06:45:22 +01:00
*/
2015-07-30 18:28:01 +02:00
package main
2014-12-09 06:45:22 +01:00
import (
"crypto/tls"
2014-12-10 00:41:50 +01:00
"flag"
"fmt"
2015-04-09 22:23:21 +02:00
"os"
"os/user"
2015-07-28 15:56:20 +02:00
"reflect"
2015-07-30 14:54:13 +02:00
"regexp"
"strings"
2015-04-09 22:23:21 +02:00
"time"
"github.com/layeh/gopus"
2014-12-09 06:45:22 +01:00
"github.com/layeh/gumble/gumble"
"github.com/layeh/gumble/gumble_ffmpeg"
2014-12-09 06:45:22 +01:00
"github.com/layeh/gumble/gumbleutil"
)
2015-05-10 07:00:24 +02:00
// mumbledj is a struct that keeps track of all aspects of the bot's current
// state.
type mumbledj struct {
2014-12-16 01:40:31 +01:00
config gumble.Config
client *gumble.Client
keepAlive chan bool
defaultChannel []string
2014-12-16 01:40:31 +01:00
conf DjConfig
queue *SongQueue
audioStream *gumble_ffmpeg.Stream
homeDir string
playlistSkips map[string][]string
cache *SongCache
verbose bool
}
2014-12-27 19:05:13 +01:00
// OnConnect event. First moves MumbleDJ into the default channel specified
// via commandline args, and moves to root channel if the channel does not exist. The current
// user's homedir path is stored, configuration is loaded, and the audio stream is set up.
func (dj *mumbledj) OnConnect(e *gumble.ConnectEvent) {
if dj.client.Channels.Find(dj.defaultChannel...) != nil {
dj.client.Self.Move(dj.client.Channels.Find(dj.defaultChannel...))
} else {
2014-12-27 19:05:13 +01:00
fmt.Println("Channel doesn't exist or one was not provided, staying in root channel...")
}
2014-12-16 01:40:31 +01:00
dj.audioStream = gumble_ffmpeg.New(dj.client)
dj.audioStream.Volume = dj.conf.Volume.DefaultVolume
dj.client.AudioEncoder.SetApplication(gopus.Audio)
dj.client.Self.SetComment(dj.conf.General.DefaultComment)
if dj.conf.Cache.Enabled {
dj.cache.Update()
go dj.cache.ClearExpired()
}
}
2014-12-27 19:05:13 +01:00
// OnDisconnect event. Terminates MumbleDJ thread.
func (dj *mumbledj) OnDisconnect(e *gumble.DisconnectEvent) {
if e.Type == gumble.DisconnectError || e.Type == gumble.DisconnectKicked {
fmt.Println("Disconnected from server... Will retry connection in 30 second intervals for 15 minutes.")
reconnectSuccess := false
for retries := 0; retries <= 30; retries++ {
fmt.Println("Retrying connection...")
if err := dj.client.Connect(); err == nil {
fmt.Println("Successfully reconnected to the server!")
reconnectSuccess = true
break
}
time.Sleep(30 * time.Second)
}
if !reconnectSuccess {
fmt.Println("Could not reconnect to server. Exiting...")
dj.keepAlive <- true
os.Exit(1)
}
} else {
dj.keepAlive <- true
}
}
2014-12-27 19:05:13 +01:00
// OnTextMessage event. Checks for command prefix, and calls parseCommand if it exists. Ignores
// the incoming message otherwise.
func (dj *mumbledj) OnTextMessage(e *gumble.TextMessageEvent) {
plainMessage := gumbleutil.PlainText(&e.TextMessage)
if len(plainMessage) != 0 {
if plainMessage[0] == dj.conf.General.CommandPrefix[0] && plainMessage != dj.conf.General.CommandPrefix {
parseCommand(e.Sender, e.Sender.Name, plainMessage[1:])
}
2014-12-16 01:40:31 +01:00
}
}
// OnUserChange event. Checks UserChange type, and adjusts items such as skiplists to reflect
// the current status of the users on the server.
func (dj *mumbledj) OnUserChange(e *gumble.UserChangeEvent) {
if e.Type.Has(gumble.UserChangeDisconnected) {
if dj.audioStream.IsPlaying() {
2015-07-29 03:05:21 +02:00
if !isNil(dj.queue.CurrentSong().Playlist()) {
dj.queue.CurrentSong().Playlist().RemoveSkip(e.User.Name)
}
dj.queue.CurrentSong().RemoveSkip(e.User.Name)
}
}
}
2015-05-10 07:00:24 +02:00
// HasPermission checks if username has the permissions to execute a command. Permissions are specified in
2014-12-27 19:05:13 +01:00
// mumbledj.gcfg.
2014-12-16 01:40:31 +01:00
func (dj *mumbledj) HasPermission(username string, command bool) bool {
if dj.conf.Permissions.AdminsEnabled && command {
for _, adminName := range dj.conf.Permissions.Admins {
if username == adminName {
return true
}
}
return false
2014-12-13 05:52:07 +01:00
}
2015-05-10 07:00:24 +02:00
return true
2014-12-13 05:52:07 +01:00
}
2015-05-10 07:00:24 +02:00
// SendPrivateMessage sends a private message to a user. Essentially just checks if a user is still in the server
// before sending them the message.
func (dj *mumbledj) SendPrivateMessage(user *gumble.User, message string) {
if targetUser := dj.client.Self.Channel.Users.Find(user.Name); targetUser != nil {
targetUser.Send(message)
}
}
// PerformStartupChecks checks the MumbleDJ installation to ensure proper usage.
func PerformStartupChecks() {
if os.Getenv("YOUTUBE_API_KEY") == "" {
fmt.Printf("You do not have a YouTube API key defined in your environment variables.\n" +
"Please see the following link for info on how to fix this: https://github.com/matthieugrieger/mumbledj#youtube-api-keys\n")
os.Exit(1)
}
}
2015-08-02 19:55:51 +02:00
// Prints out messages only if verbose flag is true
2015-07-28 13:35:52 +02:00
func Verbose(msg string) {
if dj.verbose {
2015-07-30 15:40:39 +02:00
fmt.Printf(msg + "\n")
2015-07-28 13:35:52 +02:00
}
}
2015-08-02 19:55:51 +02:00
// Checks to see if an object is nil
2015-07-28 14:29:14 +02:00
func isNil(a interface{}) bool {
2015-07-28 15:56:20 +02:00
defer func() { recover() }()
return a == nil || reflect.ValueOf(a).IsNil()
2015-07-28 14:29:14 +02:00
}
2015-07-30 14:48:53 +02:00
func RegexpFromURL(url string, patterns []string) *regexp.Regexp {
for _, pattern := range patterns {
if re, err := regexp.Compile(pattern); err == nil {
if re.MatchString(url) {
return re
}
}
}
return nil
}
2014-12-27 19:05:13 +01:00
// dj variable declaration. This is done outside of main() to allow global use.
2014-12-16 01:40:31 +01:00
var dj = mumbledj{
keepAlive: make(chan bool),
queue: NewSongQueue(),
playlistSkips: make(map[string][]string),
cache: NewSongCache(),
2014-12-09 06:45:22 +01:00
}
2014-12-10 00:41:50 +01:00
2015-07-30 16:23:05 +02:00
var web *WebServer
2015-07-30 15:01:55 +02:00
2015-05-10 07:00:24 +02:00
// main primarily performs startup tasks. Grabs and parses commandline
2014-12-27 19:05:13 +01:00
// args, sets up the gumble client and its listeners, and then connects to the server.
2014-12-10 00:41:50 +01:00
func main() {
PerformStartupChecks()
2015-04-09 03:47:39 +02:00
if currentUser, err := user.Current(); err == nil {
dj.homeDir = currentUser.HomeDir
}
if err := loadConfiguration(); err == nil {
fmt.Println("Configuration successfully loaded!")
} else {
panic(err)
}
var address, port, username, password, channel, pemCert, pemKey, accesstokens string
2015-08-12 21:23:51 +02:00
var insecure, verbose, testcode bool
2014-12-10 00:41:50 +01:00
flag.StringVar(&address, "server", "localhost", "address for Mumble server")
flag.StringVar(&port, "port", "64738", "port for Mumble server")
flag.StringVar(&username, "username", "MumbleDJ", "username of MumbleDJ on server")
flag.StringVar(&password, "password", "", "password for Mumble server (if needed)")
flag.StringVar(&channel, "channel", "root", "default channel for MumbleDJ")
flag.StringVar(&pemCert, "cert", "", "path to user PEM certificate for MumbleDJ")
flag.StringVar(&pemKey, "key", "", "path to user PEM key for MumbleDJ")
flag.StringVar(&accesstokens, "accesstokens", "", "list of access tokens for channel auth")
flag.BoolVar(&insecure, "insecure", false, "skip certificate checking")
2015-08-10 02:19:27 +02:00
flag.BoolVar(&verbose, "verbose", false, "[debug] prints out debug messages to the console")
2015-08-12 21:23:51 +02:00
flag.BoolVar(&testcode, "test", false, "[debug] tests the features of mumbledj")
2014-12-10 00:41:50 +01:00
flag.Parse()
2014-12-16 01:40:31 +01:00
dj.config = gumble.Config{
2014-12-10 00:41:50 +01:00
Username: username,
Password: password,
2014-12-16 01:40:31 +01:00
Address: address + ":" + port,
Tokens: strings.Split(accesstokens, " "),
2014-12-10 00:41:50 +01:00
}
dj.client = gumble.NewClient(&dj.config)
dj.config.TLSConfig.InsecureSkipVerify = true
if !insecure {
gumbleutil.CertificateLockFile(dj.client, fmt.Sprintf("%s/.mumbledj/cert.lock", dj.homeDir))
}
if pemCert != "" {
if pemKey == "" {
pemKey = pemCert
}
if certificate, err := tls.LoadX509KeyPair(pemCert, pemKey); err != nil {
panic(err)
} else {
dj.config.TLSConfig.Certificates = append(dj.config.TLSConfig.Certificates, certificate)
}
}
dj.defaultChannel = strings.Split(channel, "/")
2015-07-27 21:55:06 +02:00
dj.verbose = verbose
2014-12-16 01:40:31 +01:00
dj.client.Attach(gumbleutil.Listener{
2014-12-16 01:40:31 +01:00
Connect: dj.OnConnect,
Disconnect: dj.OnDisconnect,
TextMessage: dj.OnTextMessage,
UserChange: dj.OnUserChange,
})
dj.client.Attach(gumbleutil.AutoBitrate)
2014-12-16 01:40:31 +01:00
if err := dj.client.Connect(); err != nil {
fmt.Printf("Could not connect to Mumble server at %s:%s.\n", address, port)
os.Exit(1)
2015-07-29 03:04:32 +02:00
}
2015-08-12 21:23:51 +02:00
if testcode {
Verbose("Testing is enabled")
Test(password, address, port, strings.Split(accesstokens, " "))
2015-08-12 21:23:51 +02:00
}
2015-07-30 16:24:30 +02:00
if isNil(web) {
Verbose("WEB IS NIL")
}
2015-08-10 20:17:16 +02:00
<-dj.keepAlive
2014-12-10 00:41:50 +01:00
}