diff --git a/commands.go b/commands.go index 5d3d0de..8ff8846 100644 --- a/commands.go +++ b/commands.go @@ -152,6 +152,31 @@ func parseCommand(user *gumble.User, username, command string) { } else { dj.SendPrivateMessage(user, NO_PERMISSION_MSG) } + + // Shuffle command + case dj.conf.Aliases.ShuffleAlias: + if dj.HasPermission(username, dj.conf.Permissions.AdminShuffle) { + shuffleSongs(user, username) + } else { + dj.SendPrivateMessage(user, NO_PERMISSION_MSG) + } + + // Shuffleon command + case dj.conf.Aliases.ShuffleOnAlias: + if dj.HasPermission(username, dj.conf.Permissions.AdminShuffleToggle) { + toggleAutomaticShuffle(true, user, username) + } else { + dj.SendPrivateMessage(user, NO_PERMISSION_MSG) + } + + // Shuffleoff command + case dj.conf.Aliases.ShuffleOffAlias: + if dj.HasPermission(username, dj.conf.Permissions.AdminShuffleToggle) { + toggleAutomaticShuffle(false, user, username) + } else { + dj.SendPrivateMessage(user, NO_PERMISSION_MSG) + } + default: dj.SendPrivateMessage(user, COMMAND_DOESNT_EXIST_MSG) } @@ -391,3 +416,29 @@ func deleteSongs() error { } return nil } + +// shuffles the song list +func shuffleSongs(user *gumble.User, username string) { + if dj.queue.Len() > 1 { + dj.queue.ShuffleSongs() + dj.client.Self.Channel.Send(fmt.Sprintf(SHUFFLE_SUCCESS_MSG, username), false) + } else { + dj.SendPrivateMessage(user, CANT_SHUFFLE_MSG) + } +} + +// handles toggling of automatic shuffle playing +func toggleAutomaticShuffle(activate bool, user *gumble.User, username string){ + if (dj.conf.General.AutomaticShuffleOn != activate){ + dj.conf.General.AutomaticShuffleOn = activate + if (activate){ + dj.client.Self.Channel.Send(fmt.Sprintf(SHUFFLE_ON_MESSAGE, username), false) + } else{ + dj.client.Self.Channel.Send(fmt.Sprintf(SHUFFLE_OFF_MESSAGE, username), false) + } + } else if (activate){ + dj.SendPrivateMessage(user, SHUFFLE_ACTIVATED_ERROR_MESSAGE) + } else{ + dj.SendPrivateMessage(user, SHUFFLE_DEACTIVATED_ERROR_MESSAGE) + } +} diff --git a/config.gcfg b/config.gcfg index db67c05..408f51b 100644 --- a/config.gcfg +++ b/config.gcfg @@ -26,6 +26,10 @@ DefaultComment = "Hello! I am a bot. Type !help for a list of commands." # Default Value: 0 MaxSongDuration = 0 +# Is playlist shuffling enabled when the bot starts? +# Default Value: false +AutomaticShuffleOn = false + [Cache] # Cache songs as they are downloaded? @@ -55,7 +59,7 @@ LowestVolume = 0.01 # DEFAULT VALUE: 0.8 HighestVolume = 0.8 - + [Aliases] # Alias used for add command @@ -126,6 +130,17 @@ CacheSizeAlias = "cachesize" # DEFAULT VALUE: "kill" KillAlias = "kill" +# Alias used for shuffle command +# DEFAULT VALUE: "shuffle" +ShuffleAlias = "shuffle" + +# Alias used for shuffleon command +# DEFAULT VALUE: "shuffleon" +ShuffleOnAlias = "shuffleon" + +# Alias used for shuffleoff command +# DEFAULT VALUE: "shuffleoff" +ShuffleOffAlias = "shuffleoff" [Permissions] @@ -201,3 +216,12 @@ AdminCacheSize = true # Make kill an admin command? # DEFAULT VALUE: true (I recommend never changing this to false) AdminKill = true + +# Make shuffle an admin command? +# DEFAULT VALUE: true +AdminShuffle = true + + +# Make shuffleon and shuffleoff admin commands? +# DEFAULT VALUE: true +AdminShuffleToggle = true diff --git a/parseconfig.go b/parseconfig.go index 9da7ec2..c69e8b3 100644 --- a/parseconfig.go +++ b/parseconfig.go @@ -17,11 +17,12 @@ import ( // DjConfig is a Golang struct representation of mumbledj.gcfg file structure for parsing. type DjConfig struct { General struct { - CommandPrefix string - SkipRatio float32 - PlaylistSkipRatio float32 - DefaultComment string - MaxSongDuration int + CommandPrefix string + SkipRatio float32 + PlaylistSkipRatio float32 + DefaultComment string + MaxSongDuration int + AutomaticShuffleOn bool } Cache struct { Enabled bool @@ -51,25 +52,30 @@ type DjConfig struct { NumCachedAlias string CacheSizeAlias string KillAlias string + ShuffleAlias string + ShuffleOnAlias string + ShuffleOffAlias string } Permissions struct { - AdminsEnabled bool - Admins []string - AdminAdd bool - AdminAddPlaylists bool - AdminSkip bool - AdminHelp bool - AdminVolume bool - AdminMove bool - AdminReload bool - AdminReset bool - AdminNumSongs bool - AdminNextSong bool - AdminCurrentSong bool - AdminSetComment bool - AdminNumCached bool - AdminCacheSize bool - AdminKill bool + AdminsEnabled bool + Admins []string + AdminAdd bool + AdminAddPlaylists bool + AdminSkip bool + AdminHelp bool + AdminVolume bool + AdminMove bool + AdminReload bool + AdminReset bool + AdminNumSongs bool + AdminNextSong bool + AdminCurrentSong bool + AdminSetComment bool + AdminNumCached bool + AdminCacheSize bool + AdminKill bool + AdminShuffle bool + AdminShuffleToggle bool } } diff --git a/service.go b/service.go index 1e855b5..245947e 100644 --- a/service.go +++ b/service.go @@ -115,6 +115,9 @@ func FindServiceAndAdd(user *gumble.User, url string) error { // Starts playing the new song if nothing else is playing if oldLength == 0 && dj.queue.Len() != 0 && !dj.audioStream.IsPlaying() { + if (dj.conf.General.AutomaticShuffleOn){ + dj.queue.RandomNextSong(true) + } if err := dj.queue.CurrentSong().Download(); err == nil { dj.queue.CurrentSong().Play() } else { diff --git a/songqueue.go b/songqueue.go index f1bdc1c..f36e973 100644 --- a/songqueue.go +++ b/songqueue.go @@ -11,8 +11,13 @@ import ( "errors" "fmt" "time" + "math/rand" ) +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + // SongQueue type declaration. type SongQueue struct { queue []Song @@ -59,6 +64,9 @@ func (q *SongQueue) NextSong() { // PeekNext peeks at the next Song and returns it. func (q *SongQueue) PeekNext() (Song, error) { if q.Len() > 1 { + if dj.conf.General.AutomaticShuffleOn{ //Shuffle mode is active + q.RandomNextSong(false) + } return q.queue[1], nil } return nil, errors.New("There isn't a Song coming up next.") @@ -104,3 +112,22 @@ func (q *SongQueue) PrepareAndPlayNextSong() { q.OnSongFinished() } } + +// Shuffles the songqueue using inside-out algorithm +func (q *SongQueue) ShuffleSongs() { + for i := range q.queue[1:] { //Don't touch currently playing song + j := rand.Intn(i + 1) + q.queue[i + 1], q.queue[j + 1] = q.queue[j + 1], q.queue[i + 1] + } +} + +// Sets a random song as next song to be played +// queueWasEmpty wether the queue was empty before adding the last song +func (q *SongQueue) RandomNextSong(queueWasEmpty bool){ + nextSongIndex := 1 + if queueWasEmpty{ + nextSongIndex = 0 + } + swapIndex := nextSongIndex + rand.Intn(q.Len()) + q.queue[nextSongIndex], q.queue[swapIndex] = q.queue[swapIndex], q.queue[nextSongIndex] +} diff --git a/strings.go b/strings.go index 76b7541..8e19199 100644 --- a/strings.go +++ b/strings.go @@ -71,6 +71,24 @@ const CACHE_SIZE_MSG = "The cache is currently %g MB in size." // Message shown to user when they attempt to issue a cache-related command when caching is not enabled. const CACHE_NOT_ENABLED_MSG = "The cache is not currently enabled." +// Message shown to user when they attempt to shuffle the queue and it has less than 2 elements. +const CANT_SHUFFLE_MSG = "Can't shuffle the queue if there is less than 2 songs." + +// Message shown to users when the songqueue has been successfully shuffled. +const SHUFFLE_SUCCESS_MSG = "The current songqueue has been successfully shuffled by %s (starting from next song)." + +// Message shown to users when automatic shuffle is activated +const SHUFFLE_ON_MESSAGE = "%s has turned automatic shuffle on." + +// Message shown to users when automatic shuffle is deactivated +const SHUFFLE_OFF_MESSAGE = "%s has turned automatic shuffle off." + +// Message shown to user when they attempt to enable automatic shuffle while it's already activated +const SHUFFLE_ACTIVATED_ERROR_MESSAGE = "Automatic shuffle is already activated." + +// Message shown to user when they attempt to disable automatic shuffle while it's already deactivated +const SHUFFLE_DEACTIVATED_ERROR_MESSAGE = "Automatic shuffle is already deactivated." + // Message shown to channel when a song is added to the queue by a user. const SONG_ADDED_HTML = ` %s has added "%s" to the queue. @@ -107,6 +125,9 @@ const HELP_HTML = `

!reset - An admin command that resets the song queue.

!forceskip - An admin command that forces a song skip.

!forceskipplaylist - An admin command that forces a playlist skip.

+

!shuffle - An admin command that shuffles the current queue.

+

!shuffleon - An admin command that enables auto shuffling.

+

!shuffleoff - An admin command that disables auto shuffling.

!move - Moves MumbleDJ into channel if it exists.

!reload - Reloads mumbledj.gcfg configuration settings.

!setcomment - Sets the comment for the bot.