Commented code
This commit is contained in:
parent
aa642dcca5
commit
a90b4671e9
|
@ -1,7 +1,7 @@
|
|||
MumbleDJ Changelog
|
||||
==================
|
||||
|
||||
### December 27, 2014 -- `v2.0.0`
|
||||
### December 27, 2014 -- `v2.0.0, v2.1.0`
|
||||
* Reached feature parity with old version of MumbleDJ.
|
||||
* Bot is now written completely in Golang instead of Lua and Python.
|
||||
* Now uses [`gumble`](https://github.com/layeh/gumble) for interacting with Mumble instead of [`piepan`](https://github.com/layeh/piepan).
|
||||
|
@ -12,6 +12,7 @@ MumbleDJ Changelog
|
|||
* Added `mumbledj.gcfg`, where all configuration options are now stored.
|
||||
* Added a reload command, used to reload the configuration when a change is made.
|
||||
* Implemented volume control. Now changes volume while audio is playing!
|
||||
* Code is now more thoroughly commented.
|
||||
|
||||
### December 8, 2014
|
||||
* Switched from Ruby to Go, using `gumble` instead of `mumble-ruby` now.
|
||||
|
|
20
commands.go
20
commands.go
|
@ -18,6 +18,8 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// Called on text message event. Checks the message for a command string, and processes it accordingly if
|
||||
// it contains a command.
|
||||
func parseCommand(user *gumble.User, username, command string) {
|
||||
var com, argument string
|
||||
if strings.Contains(command, " ") {
|
||||
|
@ -30,6 +32,7 @@ func parseCommand(user *gumble.User, username, command string) {
|
|||
}
|
||||
|
||||
switch com {
|
||||
// Add command
|
||||
case dj.conf.Aliases.AddAlias:
|
||||
if dj.HasPermission(username, dj.conf.Permissions.AdminAdd) {
|
||||
if argument == "" {
|
||||
|
@ -52,6 +55,7 @@ func parseCommand(user *gumble.User, username, command string) {
|
|||
} else {
|
||||
user.Send(NO_PERMISSION_MSG)
|
||||
}
|
||||
// Skip command
|
||||
case dj.conf.Aliases.SkipAlias:
|
||||
if dj.HasPermission(username, dj.conf.Permissions.AdminSkip) {
|
||||
if err := skip(username, false); err == nil {
|
||||
|
@ -60,6 +64,7 @@ func parseCommand(user *gumble.User, username, command string) {
|
|||
} else {
|
||||
user.Send(NO_PERMISSION_MSG)
|
||||
}
|
||||
// Forceskip command
|
||||
case dj.conf.Aliases.AdminSkipAlias:
|
||||
if dj.HasPermission(username, true) {
|
||||
if err := skip(username, true); err == nil {
|
||||
|
@ -68,6 +73,7 @@ func parseCommand(user *gumble.User, username, command string) {
|
|||
} else {
|
||||
user.Send(NO_PERMISSION_MSG)
|
||||
}
|
||||
// Volume command
|
||||
case dj.conf.Aliases.VolumeAlias:
|
||||
if dj.HasPermission(username, dj.conf.Permissions.AdminVolume) {
|
||||
if argument == "" {
|
||||
|
@ -82,6 +88,7 @@ func parseCommand(user *gumble.User, username, command string) {
|
|||
} else {
|
||||
user.Send(NO_PERMISSION_MSG)
|
||||
}
|
||||
// Move command
|
||||
case dj.conf.Aliases.MoveAlias:
|
||||
if dj.HasPermission(username, dj.conf.Permissions.AdminMove) {
|
||||
if argument == "" {
|
||||
|
@ -96,6 +103,7 @@ func parseCommand(user *gumble.User, username, command string) {
|
|||
} else {
|
||||
user.Send(NO_PERMISSION_MSG)
|
||||
}
|
||||
// Reload command
|
||||
case dj.conf.Aliases.ReloadAlias:
|
||||
if dj.HasPermission(username, dj.conf.Permissions.AdminReload) {
|
||||
err := loadConfiguration()
|
||||
|
@ -107,6 +115,7 @@ func parseCommand(user *gumble.User, username, command string) {
|
|||
} else {
|
||||
user.Send(NO_PERMISSION_MSG)
|
||||
}
|
||||
// Kill command
|
||||
case dj.conf.Aliases.KillAlias:
|
||||
if dj.HasPermission(username, dj.conf.Permissions.AdminKill) {
|
||||
if err := kill(); err == nil {
|
||||
|
@ -123,6 +132,8 @@ func parseCommand(user *gumble.User, username, command string) {
|
|||
}
|
||||
}
|
||||
|
||||
// Performs add functionality. Checks input URL for YouTube format, and adds
|
||||
// the URL to the queue if the format matches.
|
||||
func add(user, url string) (string, error) {
|
||||
youtubePatterns := []string{
|
||||
`https?:\/\/www\.youtube\.com\/watch\?v=([\w-]+)`,
|
||||
|
@ -156,6 +167,8 @@ func add(user, url string) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Performs skip functionality. Adds a skip to the skippers slice for the current song, and then
|
||||
// evaluates if a skip should be performed. Both skip and forceskip are implemented here.
|
||||
func skip(user string, admin bool) error {
|
||||
if err := dj.currentSong.AddSkip(user); err == nil {
|
||||
if dj.currentSong.SkipReached(len(dj.client.Self().Channel().Users())) || admin {
|
||||
|
@ -173,6 +186,9 @@ func skip(user string, admin bool) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Performs volume functionality. Checks input value against LowestVolume and HighestVolume from
|
||||
// config to determine if the volume should be applied. If in the correct range, the new volume
|
||||
// is applied and is immediately in effect.
|
||||
func volume(user, value string) error {
|
||||
if parsedVolume, err := strconv.ParseFloat(value, 32); err == nil {
|
||||
newVolume := float32(parsedVolume)
|
||||
|
@ -187,6 +203,8 @@ func volume(user, value string) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Performs move functionality. Determines if the supplied channel is valid and moves the bot
|
||||
// to the channel if it is.
|
||||
func move(channel string) error {
|
||||
if dj.client.Channels().Find(channel) != nil {
|
||||
dj.client.Self().Move(dj.client.Channels().Find(channel))
|
||||
|
@ -196,6 +214,8 @@ func move(channel string) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Performs kill functionality. First cleans the ~/.mumbledj/songs directory to get rid of any
|
||||
// excess m4a files. The bot then safely disconnects from the server.
|
||||
func kill() error {
|
||||
songsDir := fmt.Sprintf("%s/.mumbledj/songs", dj.homeDir)
|
||||
if err := os.RemoveAll(songsDir); err != nil {
|
||||
|
|
17
main.go
17
main.go
|
@ -29,11 +29,14 @@ type mumbledj struct {
|
|||
homeDir string
|
||||
}
|
||||
|
||||
// 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 {
|
||||
fmt.Println("Channel doesn't exist, staying in root channel...")
|
||||
fmt.Println("Channel doesn't exist or one was not provided, staying in root channel...")
|
||||
}
|
||||
|
||||
if currentUser, err := user.Current(); err == nil {
|
||||
|
@ -55,16 +58,21 @@ func (dj *mumbledj) OnConnect(e *gumble.ConnectEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
// OnDisconnect event. Terminates MumbleDJ thread.
|
||||
func (dj *mumbledj) OnDisconnect(e *gumble.DisconnectEvent) {
|
||||
dj.keepAlive <- true
|
||||
}
|
||||
|
||||
// OnTextMessage event. Checks for command prefix, and calls parseCommand if it exists. Ignores
|
||||
// the incoming message otherwise.
|
||||
func (dj *mumbledj) OnTextMessage(e *gumble.TextMessageEvent) {
|
||||
if e.Message[0] == '!' {
|
||||
if e.Message[0] == dj.conf.General.CommandPrefix {
|
||||
parseCommand(e.Sender, e.Sender.Name(), e.Message[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if username has the permissions to execute a command. Permissions are specified in
|
||||
// mumbledj.gcfg.
|
||||
func (dj *mumbledj) HasPermission(username string, command bool) bool {
|
||||
if dj.conf.Permissions.AdminsEnabled && command {
|
||||
for _, adminName := range dj.conf.Permissions.Admins {
|
||||
|
@ -78,6 +86,8 @@ func (dj *mumbledj) HasPermission(username string, command bool) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// OnSongFinished event. Deletes song that just finished playing, then queues, downloads, and plays
|
||||
// the next song if it exists.
|
||||
func (dj *mumbledj) OnSongFinished() {
|
||||
if err := dj.currentSong.Delete(); err == nil {
|
||||
if dj.queue.Len() != 0 {
|
||||
|
@ -95,11 +105,14 @@ func (dj *mumbledj) OnSongFinished() {
|
|||
}
|
||||
}
|
||||
|
||||
// dj variable declaration. This is done outside of main() to allow global use.
|
||||
var dj = mumbledj{
|
||||
keepAlive: make(chan bool),
|
||||
queue: NewSongQueue(),
|
||||
}
|
||||
|
||||
// Main function, but only really performs startup tasks. Grabs and parses commandline
|
||||
// args, sets up the gumble client and its listeners, and then connects to the server.
|
||||
func main() {
|
||||
var address, port, username, password, channel string
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// Golang struct representation of mumbledj.gcfg file structure for parsing.
|
||||
type DjConfig struct {
|
||||
General struct {
|
||||
CommandPrefix string
|
||||
|
@ -44,6 +45,7 @@ type DjConfig struct {
|
|||
}
|
||||
}
|
||||
|
||||
// Loads mumbledj.gcfg into dj.conf, a variable of type DjConfig.
|
||||
func loadConfiguration() error {
|
||||
if gcfg.ReadFileInto(&dj.conf, fmt.Sprintf("%s/.mumbledj/config/mumbledj.gcfg", dj.homeDir)) == nil {
|
||||
return nil
|
||||
|
|
14
song.go
14
song.go
|
@ -19,6 +19,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// Song type declaration.
|
||||
type Song struct {
|
||||
submitter string
|
||||
title string
|
||||
|
@ -28,6 +29,8 @@ type Song struct {
|
|||
skippers []string
|
||||
}
|
||||
|
||||
// Returns a new Song type. Before returning the new type, the song's metadata is collected
|
||||
// via the YouTube Gdata API.
|
||||
func NewSong(user, id string) *Song {
|
||||
jsonUrl := fmt.Sprintf("http://gdata.youtube.com/feeds/api/videos/%s?v=2&alt=jsonc", id)
|
||||
jsonString := ""
|
||||
|
@ -59,6 +62,7 @@ func NewSong(user, id string) *Song {
|
|||
return song
|
||||
}
|
||||
|
||||
// Downloads the song via youtube-dl. All downloaded songs are stored in ~/.mumbledj/songs and should be automatically cleaned.
|
||||
func (s *Song) Download() error {
|
||||
cmd := exec.Command("youtube-dl", "--output", fmt.Sprintf(`~/.mumbledj/songs/%s.m4a`, s.youtubeId), "--format", "m4a", s.youtubeId)
|
||||
if err := cmd.Run(); err == nil {
|
||||
|
@ -68,11 +72,14 @@ func (s *Song) Download() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Plays the song. Once the song is playing, a notification is displayed in a text message that features the video thumbnail, URL, title,
|
||||
// duration, and submitter.
|
||||
func (s *Song) Play() {
|
||||
dj.audioStream.Play(fmt.Sprintf("%s/.mumbledj/songs/%s.m4a", dj.homeDir, s.youtubeId))
|
||||
dj.client.Self().Channel().Send(fmt.Sprintf(NOW_PLAYING_HTML, s.thumbnailUrl, s.youtubeId, s.title, s.duration, s.submitter), false)
|
||||
}
|
||||
|
||||
// Deletes the song from ~/.mumbledj/songs.
|
||||
func (s *Song) Delete() error {
|
||||
filePath := fmt.Sprintf("%s/.mumbledj/songs/%s.m4a", dj.homeDir, s.youtubeId)
|
||||
if _, err := os.Stat(filePath); err == nil {
|
||||
|
@ -86,6 +93,8 @@ func (s *Song) Delete() error {
|
|||
}
|
||||
}
|
||||
|
||||
// Adds a skip to the skippers slice. If the user is already in the slice AddSkip() returns
|
||||
// an error and does not add a duplicate skip.
|
||||
func (s *Song) AddSkip(username string) error {
|
||||
for _, user := range s.skippers {
|
||||
if username == user {
|
||||
|
@ -96,6 +105,8 @@ func (s *Song) AddSkip(username string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Removes a skip from the skippers slice. If username is not in the slice, an error is
|
||||
// returned.
|
||||
func (s *Song) RemoveSkip(username string) error {
|
||||
for i, user := range s.skippers {
|
||||
if username == user {
|
||||
|
@ -106,6 +117,9 @@ func (s *Song) RemoveSkip(username string) error {
|
|||
return errors.New("This user has not skipped the song.")
|
||||
}
|
||||
|
||||
// Calculates current skip ratio based on number of users within MumbleDJ's channel and the
|
||||
// amount of values in the skippers slice. If the value is greater than or equal to the skip ratio
|
||||
// defined in mumbledj.gcfg, the function returns true. Returns false otherwise.
|
||||
func (s *Song) SkipReached(channelUsers int) bool {
|
||||
if float32(len(s.skippers))/float32(channelUsers) >= dj.conf.General.SkipRatio {
|
||||
return true
|
||||
|
|
|
@ -11,16 +11,19 @@ import (
|
|||
"errors"
|
||||
)
|
||||
|
||||
// SongQueue type declaration. Serves as a wrapper around the queue structure defined in queue.go.
|
||||
type SongQueue struct {
|
||||
queue *Queue
|
||||
}
|
||||
|
||||
// Initializes a new queue and returns the new SongQueue.
|
||||
func NewSongQueue() *SongQueue {
|
||||
return &SongQueue{
|
||||
queue: NewQueue(),
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a song to the SongQueue.
|
||||
func (q *SongQueue) AddSong(s *Song) error {
|
||||
beforeLen := q.queue.Len()
|
||||
q.queue.Push(s)
|
||||
|
@ -31,10 +34,13 @@ func (q *SongQueue) AddSong(s *Song) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Moves to the next song in SongQueue. NextSong() pops the first value of the queue, and is stored
|
||||
// in dj.currentSong.
|
||||
func (q *SongQueue) NextSong() *Song {
|
||||
return q.queue.Poll().(*Song)
|
||||
}
|
||||
|
||||
// Returns the length of the SongQueue.
|
||||
func (q *SongQueue) Len() int {
|
||||
return q.queue.Len()
|
||||
}
|
||||
|
|
Reference in a new issue