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
|
|
|
*/
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2015-02-10 00:08:09 +01:00
|
|
|
"crypto/tls"
|
2014-12-10 00:41:50 +01:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
2015-04-09 22:23:21 +02:00
|
|
|
"os"
|
|
|
|
"os/user"
|
2015-05-14 11:15:18 +02:00
|
|
|
"strings"
|
2015-04-09 22:23:21 +02:00
|
|
|
"time"
|
|
|
|
|
2015-01-14 20:56:46 +01:00
|
|
|
"github.com/layeh/gopus"
|
2014-12-09 06:45:22 +01:00
|
|
|
"github.com/layeh/gumble/gumble"
|
2014-12-27 09:25:49 +01:00
|
|
|
"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.
|
2014-12-13 00:42:59 +01:00
|
|
|
type mumbledj struct {
|
2014-12-16 01:40:31 +01:00
|
|
|
config gumble.Config
|
|
|
|
client *gumble.Client
|
|
|
|
keepAlive chan bool
|
2015-05-14 11:15:18 +02:00
|
|
|
defaultChannel []string
|
2014-12-16 01:40:31 +01:00
|
|
|
conf DjConfig
|
2014-12-19 01:28:27 +01:00
|
|
|
queue *SongQueue
|
2014-12-27 09:25:49 +01:00
|
|
|
audioStream *gumble_ffmpeg.Stream
|
|
|
|
homeDir string
|
2015-02-12 22:27:04 +01:00
|
|
|
playlistSkips map[string][]string
|
2015-02-18 00:43:20 +01:00
|
|
|
cache *SongCache
|
2015-07-27 21:44:09 +02:00
|
|
|
verbose bool
|
2014-12-13 00:42:59 +01:00
|
|
|
}
|
|
|
|
|
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.
|
2014-12-13 00:42:59 +01:00
|
|
|
func (dj *mumbledj) OnConnect(e *gumble.ConnectEvent) {
|
2015-05-14 11:15:18 +02:00
|
|
|
if dj.client.Channels.Find(dj.defaultChannel...) != nil {
|
|
|
|
dj.client.Self.Move(dj.client.Channels.Find(dj.defaultChannel...))
|
2014-12-15 05:35:44 +01:00
|
|
|
} 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-15 05:35:44 +01:00
|
|
|
}
|
2014-12-16 01:40:31 +01:00
|
|
|
|
2015-05-10 06:45:00 +02:00
|
|
|
dj.audioStream = gumble_ffmpeg.New(dj.client)
|
|
|
|
dj.audioStream.Volume = dj.conf.Volume.DefaultVolume
|
2015-01-14 20:56:46 +01:00
|
|
|
|
2015-02-12 00:25:47 +01:00
|
|
|
dj.client.AudioEncoder.SetApplication(gopus.Audio)
|
2015-02-10 00:08:09 +01:00
|
|
|
|
2015-02-12 00:25:47 +01:00
|
|
|
dj.client.Self.SetComment(dj.conf.General.DefaultComment)
|
2015-02-18 00:43:20 +01:00
|
|
|
|
|
|
|
if dj.conf.Cache.Enabled {
|
|
|
|
dj.cache.Update()
|
|
|
|
go dj.cache.ClearExpired()
|
|
|
|
}
|
2014-12-13 00:42:59 +01:00
|
|
|
}
|
|
|
|
|
2014-12-27 19:05:13 +01:00
|
|
|
// OnDisconnect event. Terminates MumbleDJ thread.
|
2014-12-13 00:42:59 +01:00
|
|
|
func (dj *mumbledj) OnDisconnect(e *gumble.DisconnectEvent) {
|
2015-02-26 05:47:53 +01:00
|
|
|
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-13 00:42:59 +01:00
|
|
|
}
|
|
|
|
|
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.
|
2014-12-13 00:42:59 +01:00
|
|
|
func (dj *mumbledj) OnTextMessage(e *gumble.TextMessageEvent) {
|
2015-02-07 23:10:45 +01:00
|
|
|
plainMessage := gumbleutil.PlainText(&e.TextMessage)
|
2015-02-10 00:12:04 +01:00
|
|
|
if len(plainMessage) != 0 {
|
|
|
|
if plainMessage[0] == dj.conf.General.CommandPrefix[0] && plainMessage != dj.conf.General.CommandPrefix {
|
2015-02-12 00:25:47 +01:00
|
|
|
parseCommand(e.Sender, e.Sender.Name, plainMessage[1:])
|
2015-02-10 00:12:04 +01:00
|
|
|
}
|
2014-12-16 01:40:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-25 20:45:50 +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) {
|
2015-01-25 23:04:47 +01:00
|
|
|
if dj.audioStream.IsPlaying() {
|
2015-04-16 08:32:32 +02:00
|
|
|
if dj.queue.CurrentSong().Playlist() != nil {
|
|
|
|
dj.queue.CurrentSong().Playlist().RemoveSkip(e.User.Name)
|
2015-01-25 23:04:47 +01:00
|
|
|
}
|
2015-02-12 22:27:04 +01:00
|
|
|
dj.queue.CurrentSong().RemoveSkip(e.User.Name)
|
2015-01-25 20:45:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
2015-01-31 03:26:51 +01:00
|
|
|
// before sending them the message.
|
|
|
|
func (dj *mumbledj) SendPrivateMessage(user *gumble.User, message string) {
|
2015-02-12 00:25:47 +01:00
|
|
|
if targetUser := dj.client.Self.Channel.Users.Find(user.Name); targetUser != nil {
|
2015-01-31 03:26:51 +01:00
|
|
|
targetUser.Send(message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-18 01:54:30 +02:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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{
|
2015-02-12 22:27:04 +01:00
|
|
|
keepAlive: make(chan bool),
|
|
|
|
queue: NewSongQueue(),
|
|
|
|
playlistSkips: make(map[string][]string),
|
2015-02-18 00:43:20 +01:00
|
|
|
cache: NewSongCache(),
|
2014-12-09 06:45:22 +01:00
|
|
|
}
|
2014-12-10 00:41:50 +01:00
|
|
|
|
2015-07-27 23:48:56 +02:00
|
|
|
var services []Service
|
2015-07-27 23:38:21 +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() {
|
2015-03-28 01:56:47 +01:00
|
|
|
|
2015-04-18 01:54:30 +02:00
|
|
|
PerformStartupChecks()
|
2015-07-27 23:48:56 +02:00
|
|
|
services = []Service{Youtube{}}
|
2015-04-18 01:54:30 +02:00
|
|
|
|
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)
|
|
|
|
}
|
2015-03-28 01:56:47 +01:00
|
|
|
|
2015-05-14 11:15:18 +02:00
|
|
|
var address, port, username, password, channel, pemCert, pemKey, accesstokens string
|
2015-07-27 21:44:09 +02:00
|
|
|
var insecure, verbose bool
|
2014-12-19 01:28:27 +01:00
|
|
|
|
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)")
|
2014-12-20 06:56:30 +01:00
|
|
|
flag.StringVar(&channel, "channel", "root", "default channel for MumbleDJ")
|
2015-02-08 03:20:00 +01:00
|
|
|
flag.StringVar(&pemCert, "cert", "", "path to user PEM certificate for MumbleDJ")
|
|
|
|
flag.StringVar(&pemKey, "key", "", "path to user PEM key for MumbleDJ")
|
2015-05-14 11:15:18 +02:00
|
|
|
flag.StringVar(&accesstokens, "accesstokens", "", "list of access tokens for channel auth")
|
2015-02-20 01:48:40 +01:00
|
|
|
flag.BoolVar(&insecure, "insecure", false, "skip certificate checking")
|
2015-07-27 21:44:09 +02:00
|
|
|
flag.BoolVar(&verbose, "verbose", false, "prints out debug messages to the console")
|
2014-12-10 00:41:50 +01:00
|
|
|
flag.Parse()
|
2014-12-16 01:40:31 +01:00
|
|
|
|
2014-12-13 00:42:59 +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,
|
2015-05-14 11:15:18 +02:00
|
|
|
Tokens: strings.Split(accesstokens, " "),
|
2014-12-10 00:41:50 +01:00
|
|
|
}
|
2015-02-20 01:48:40 +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))
|
|
|
|
}
|
2015-02-08 03:20:00 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-14 11:15:18 +02:00
|
|
|
dj.defaultChannel = strings.Split(channel, "/")
|
2015-07-27 21:55:06 +02:00
|
|
|
dj.verbose = verbose
|
2014-12-16 01:40:31 +01:00
|
|
|
|
2014-12-13 00:42:59 +01:00
|
|
|
dj.client.Attach(gumbleutil.Listener{
|
2014-12-16 01:40:31 +01:00
|
|
|
Connect: dj.OnConnect,
|
|
|
|
Disconnect: dj.OnDisconnect,
|
2014-12-13 00:42:59 +01:00
|
|
|
TextMessage: dj.OnTextMessage,
|
2015-01-25 20:45:50 +01:00
|
|
|
UserChange: dj.OnUserChange,
|
2014-12-13 00:42:59 +01:00
|
|
|
})
|
2015-01-05 21:05:38 +01:00
|
|
|
dj.client.Attach(gumbleutil.AutoBitrate)
|
2014-12-16 01:40:31 +01:00
|
|
|
|
2014-12-13 00:42:59 +01:00
|
|
|
if err := dj.client.Connect(); err != nil {
|
2015-02-08 03:25:59 +01:00
|
|
|
fmt.Printf("Could not connect to Mumble server at %s:%s.\n", address, port)
|
|
|
|
os.Exit(1)
|
2014-12-10 00:41:50 +01:00
|
|
|
}
|
2014-12-16 01:40:31 +01:00
|
|
|
|
2014-12-13 00:42:59 +01:00
|
|
|
<-dj.keepAlive
|
2014-12-10 00:41:50 +01:00
|
|
|
}
|