Added ability to add YouTube playlists to queue
This commit is contained in:
parent
39f7b8dcab
commit
aa86b570a0
112
commands.go
112
commands.go
|
@ -22,8 +22,8 @@ import (
|
||||||
// it contains a command.
|
// it contains a command.
|
||||||
func parseCommand(user *gumble.User, username, command string) {
|
func parseCommand(user *gumble.User, username, command string) {
|
||||||
var com, argument string
|
var com, argument string
|
||||||
if strings.Contains(command, " ") {
|
sanitizedCommand := sanitize.HTML(command)
|
||||||
sanitizedCommand := sanitize.HTML(command)
|
if strings.Contains(sanitizedCommand, " ") {
|
||||||
parsedCommand := strings.Split(sanitizedCommand, " ")
|
parsedCommand := strings.Split(sanitizedCommand, " ")
|
||||||
com, argument = parsedCommand[0], parsedCommand[1]
|
com, argument = parsedCommand[0], parsedCommand[1]
|
||||||
} else {
|
} else {
|
||||||
|
@ -42,14 +42,28 @@ func parseCommand(user *gumble.User, username, command string) {
|
||||||
// Skip command
|
// Skip command
|
||||||
case dj.conf.Aliases.SkipAlias:
|
case dj.conf.Aliases.SkipAlias:
|
||||||
if dj.HasPermission(username, dj.conf.Permissions.AdminSkip) {
|
if dj.HasPermission(username, dj.conf.Permissions.AdminSkip) {
|
||||||
skip(username, false)
|
skip(user, username, false, false)
|
||||||
|
} else {
|
||||||
|
user.Send(NO_PERMISSION_MSG)
|
||||||
|
}
|
||||||
|
// Skip playlist command
|
||||||
|
case dj.conf.Aliases.SkipPlaylistAlias:
|
||||||
|
if dj.HasPermission(username, dj.conf.Permissions.AdminAddPlaylists) {
|
||||||
|
skip(user, username, false, true)
|
||||||
} else {
|
} else {
|
||||||
user.Send(NO_PERMISSION_MSG)
|
user.Send(NO_PERMISSION_MSG)
|
||||||
}
|
}
|
||||||
// Forceskip command
|
// Forceskip command
|
||||||
case dj.conf.Aliases.AdminSkipAlias:
|
case dj.conf.Aliases.AdminSkipAlias:
|
||||||
if dj.HasPermission(username, true) {
|
if dj.HasPermission(username, true) {
|
||||||
skip(username, true)
|
skip(user, username, true, false)
|
||||||
|
} else {
|
||||||
|
user.Send(NO_PERMISSION_MSG)
|
||||||
|
}
|
||||||
|
// Playlist forceskip command
|
||||||
|
case dj.conf.Aliases.AdminSkipPlaylistAlias:
|
||||||
|
if dj.HasPermission(username, true) {
|
||||||
|
skip(user, username, true, true)
|
||||||
} else {
|
} else {
|
||||||
user.Send(NO_PERMISSION_MSG)
|
user.Send(NO_PERMISSION_MSG)
|
||||||
}
|
}
|
||||||
|
@ -114,45 +128,93 @@ func add(user *gumble.User, username, url string) {
|
||||||
|
|
||||||
if matchFound {
|
if matchFound {
|
||||||
newSong := NewSong(username, shortUrl)
|
newSong := NewSong(username, shortUrl)
|
||||||
if err := dj.queue.AddSong(newSong); err == nil {
|
if err := dj.queue.AddItem(newSong); err == nil {
|
||||||
dj.client.Self().Channel().Send(fmt.Sprintf(SONG_ADDED_HTML, username, newSong.title), false)
|
dj.client.Self().Channel().Send(fmt.Sprintf(SONG_ADDED_HTML, username, newSong.title), false)
|
||||||
if dj.queue.Len() == 1 && !dj.audioStream.IsPlaying() {
|
if dj.queue.Len() == 1 && !dj.audioStream.IsPlaying() {
|
||||||
dj.currentSong = dj.queue.NextSong()
|
if err := dj.queue.CurrentItem().(*Song).Download(); err == nil {
|
||||||
if err := dj.currentSong.Download(); err == nil {
|
dj.queue.CurrentItem().(*Song).Play()
|
||||||
dj.currentSong.Play()
|
|
||||||
} else {
|
} else {
|
||||||
user.Send(AUDIO_FAIL_MSG)
|
user.Send(AUDIO_FAIL_MSG)
|
||||||
dj.currentSong.Delete()
|
dj.queue.CurrentItem().(*Song).Delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
panic(errors.New("Could not add the Song to the queue."))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user.Send(INVALID_URL_MSG)
|
// Check to see if we have a playlist URL instead.
|
||||||
|
youtubePlaylistPattern := `https?:\/\/www\.youtube\.com\/playlist\?list=(\w+)`
|
||||||
|
if re, err := regexp.Compile(youtubePlaylistPattern); err == nil {
|
||||||
|
if re.MatchString(url) {
|
||||||
|
if dj.HasPermission(username, dj.conf.Permissions.AdminAddPlaylists) {
|
||||||
|
shortUrl = re.FindStringSubmatch(url)[1]
|
||||||
|
newPlaylist := NewPlaylist(username, shortUrl)
|
||||||
|
if dj.queue.AddItem(newPlaylist); err == nil {
|
||||||
|
dj.client.Self().Channel().Send(fmt.Sprintf(PLAYLIST_ADDED_HTML, username, newPlaylist.title), false)
|
||||||
|
if dj.queue.Len() == 1 && !dj.audioStream.IsPlaying() {
|
||||||
|
if err := dj.queue.CurrentItem().(*Playlist).songs.CurrentItem().(*Song).Download(); err == nil {
|
||||||
|
dj.queue.CurrentItem().(*Playlist).songs.CurrentItem().(*Song).Play()
|
||||||
|
} else {
|
||||||
|
user.Send(AUDIO_FAIL_MSG)
|
||||||
|
dj.queue.CurrentItem().(*Playlist).songs.CurrentItem().(*Song).Delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user.Send(NO_PLAYLIST_PERMISSION_MSG)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user.Send(INVALID_URL_MSG)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs skip functionality. Adds a skip to the skippers slice for the current song, and then
|
// 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.
|
// evaluates if a skip should be performed. Both skip and forceskip are implemented here.
|
||||||
func skip(user string, admin bool) {
|
func skip(user *gumble.User, username string, admin, playlistSkip bool) {
|
||||||
if err := dj.currentSong.AddSkip(user); err == nil {
|
if playlistSkip {
|
||||||
if admin {
|
if dj.queue.CurrentItem().ItemType() == "playlist" {
|
||||||
dj.client.Self().Channel().Send(ADMIN_SONG_SKIP_MSG, false)
|
if err := dj.queue.CurrentItem().AddSkip(username); err == nil {
|
||||||
} else {
|
if admin {
|
||||||
dj.client.Self().Channel().Send(fmt.Sprintf(SKIP_ADDED_HTML, user), false)
|
dj.client.Self().Channel().Send(ADMIN_PLAYLIST_SKIP_MSG, false)
|
||||||
}
|
} else {
|
||||||
if dj.currentSong.SkipReached(len(dj.client.Self().Channel().Users())) || admin {
|
dj.client.Self().Channel().Send(fmt.Sprintf(PLAYLIST_SKIP_ADDED_HTML, username), false)
|
||||||
dj.client.Self().Channel().Send(SONG_SKIPPED_HTML, false)
|
}
|
||||||
if err := dj.audioStream.Stop(); err == nil {
|
if dj.queue.CurrentItem().SkipReached(len(dj.client.Self().Channel().Users())) || admin {
|
||||||
dj.OnSongFinished()
|
dj.queue.CurrentItem().(*Playlist).skipped = true
|
||||||
|
dj.client.Self().Channel().Send(PLAYLIST_SKIPPED_HTML, false)
|
||||||
|
if err := dj.audioStream.Stop(); err != nil {
|
||||||
|
panic(errors.New("An error occurred while stopping the current song."))
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
panic(errors.New("An error occurred while stopping the current song."))
|
panic(errors.New("An error occurred while adding a skip to the current playlist."))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
user.Send(NO_PLAYLIST_PLAYING_MSG)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic(errors.New("An error occurred while adding a skip to the current song."))
|
var currentItem QueueItem
|
||||||
|
if dj.queue.CurrentItem().ItemType() == "playlist" {
|
||||||
|
currentItem = dj.queue.CurrentItem().(*Playlist).songs.CurrentItem()
|
||||||
|
} else {
|
||||||
|
currentItem = dj.queue.CurrentItem()
|
||||||
|
}
|
||||||
|
if err := currentItem.AddSkip(username); err == nil {
|
||||||
|
if admin {
|
||||||
|
dj.client.Self().Channel().Send(ADMIN_SONG_SKIP_MSG, false)
|
||||||
|
} else {
|
||||||
|
dj.client.Self().Channel().Send(fmt.Sprintf(SKIP_ADDED_HTML, username), false)
|
||||||
|
}
|
||||||
|
if currentItem.SkipReached(len(dj.client.Self().Channel().Users())) || admin {
|
||||||
|
dj.client.Self().Channel().Send(SONG_SKIPPED_HTML, false)
|
||||||
|
if err := dj.audioStream.Stop(); err != nil {
|
||||||
|
panic(errors.New("An error occurred while stopping the current song."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic(errors.New("An error occurred while adding a skip to the current song."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
25
main.go
25
main.go
|
@ -24,7 +24,6 @@ type mumbledj struct {
|
||||||
defaultChannel string
|
defaultChannel string
|
||||||
conf DjConfig
|
conf DjConfig
|
||||||
queue *SongQueue
|
queue *SongQueue
|
||||||
currentSong *Song
|
|
||||||
audioStream *gumble_ffmpeg.Stream
|
audioStream *gumble_ffmpeg.Stream
|
||||||
homeDir string
|
homeDir string
|
||||||
}
|
}
|
||||||
|
@ -51,7 +50,7 @@ func (dj *mumbledj) OnConnect(e *gumble.ConnectEvent) {
|
||||||
|
|
||||||
if audioStream, err := gumble_ffmpeg.New(dj.client); err == nil {
|
if audioStream, err := gumble_ffmpeg.New(dj.client); err == nil {
|
||||||
dj.audioStream = audioStream
|
dj.audioStream = audioStream
|
||||||
dj.audioStream.Done = dj.OnSongFinished
|
dj.audioStream.Done = dj.queue.OnItemFinished
|
||||||
dj.audioStream.SetVolume(dj.conf.Volume.DefaultVolume)
|
dj.audioStream.SetVolume(dj.conf.Volume.DefaultVolume)
|
||||||
} else {
|
} else {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -86,28 +85,6 @@ 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 {
|
|
||||||
dj.currentSong = dj.queue.NextSong()
|
|
||||||
if dj.currentSong != nil {
|
|
||||||
if err := dj.currentSong.Download(); err == nil {
|
|
||||||
dj.currentSong.Play()
|
|
||||||
} else {
|
|
||||||
username := dj.currentSong.submitter
|
|
||||||
user := dj.client.Self().Channel().Users().Find(username)
|
|
||||||
user.Send(AUDIO_FAIL_MSG)
|
|
||||||
dj.OnSongFinished()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dj variable declaration. This is done outside of main() to allow global use.
|
// dj variable declaration. This is done outside of main() to allow global use.
|
||||||
var dj = mumbledj{
|
var dj = mumbledj{
|
||||||
keepAlive: make(chan bool),
|
keepAlive: make(chan bool),
|
||||||
|
|
|
@ -13,6 +13,10 @@ CommandPrefix = "!"
|
||||||
# DEFAULT VALUE: 0.5
|
# DEFAULT VALUE: 0.5
|
||||||
SkipRatio = 0.5
|
SkipRatio = 0.5
|
||||||
|
|
||||||
|
# Ratio that must be met or exceeded to trigger a playlist skip
|
||||||
|
# DEFAULT VALUE: 0.5
|
||||||
|
PlaylistSkipRatio = 0.5
|
||||||
|
|
||||||
|
|
||||||
[Volume]
|
[Volume]
|
||||||
|
|
||||||
|
@ -39,6 +43,10 @@ AddAlias = "add"
|
||||||
# DEFAULT VALUE: "skip"
|
# DEFAULT VALUE: "skip"
|
||||||
SkipAlias = "skip"
|
SkipAlias = "skip"
|
||||||
|
|
||||||
|
# Alias used for playlist skip command
|
||||||
|
# DEFAULT VALUE: "skipplaylist"
|
||||||
|
SkipPlaylistAlias = "skipplaylist"
|
||||||
|
|
||||||
# Alias used for admin skip command
|
# Alias used for admin skip command
|
||||||
# DEFAULT VALUE: "forceskip"
|
# DEFAULT VALUE: "forceskip"
|
||||||
AdminSkipAlias = "forceskip"
|
AdminSkipAlias = "forceskip"
|
||||||
|
@ -79,6 +87,10 @@ Admins = "Matt"
|
||||||
# DEFAULT VALUE: false
|
# DEFAULT VALUE: false
|
||||||
AdminAdd = false
|
AdminAdd = false
|
||||||
|
|
||||||
|
# Make playlist adds an admin only action?
|
||||||
|
# DEFAULT VALUE: false
|
||||||
|
AdminAddPlaylists = false
|
||||||
|
|
||||||
# Make skip an admin command?
|
# Make skip an admin command?
|
||||||
# DEFAULT VALUE: false
|
# DEFAULT VALUE: false
|
||||||
AdminSkip = false
|
AdminSkip = false
|
||||||
|
|
|
@ -16,8 +16,9 @@ import (
|
||||||
// Golang struct representation of mumbledj.gcfg file structure for parsing.
|
// Golang struct representation of mumbledj.gcfg file structure for parsing.
|
||||||
type DjConfig struct {
|
type DjConfig struct {
|
||||||
General struct {
|
General struct {
|
||||||
CommandPrefix string
|
CommandPrefix string
|
||||||
SkipRatio float32
|
SkipRatio float32
|
||||||
|
PlaylistSkipRatio float32
|
||||||
}
|
}
|
||||||
Volume struct {
|
Volume struct {
|
||||||
DefaultVolume float32
|
DefaultVolume float32
|
||||||
|
@ -25,23 +26,26 @@ type DjConfig struct {
|
||||||
HighestVolume float32
|
HighestVolume float32
|
||||||
}
|
}
|
||||||
Aliases struct {
|
Aliases struct {
|
||||||
AddAlias string
|
AddAlias string
|
||||||
SkipAlias string
|
SkipAlias string
|
||||||
AdminSkipAlias string
|
SkipPlaylistAlias string
|
||||||
VolumeAlias string
|
AdminSkipAlias string
|
||||||
MoveAlias string
|
AdminSkipPlaylistAlias string
|
||||||
ReloadAlias string
|
VolumeAlias string
|
||||||
KillAlias string
|
MoveAlias string
|
||||||
|
ReloadAlias string
|
||||||
|
KillAlias string
|
||||||
}
|
}
|
||||||
Permissions struct {
|
Permissions struct {
|
||||||
AdminsEnabled bool
|
AdminsEnabled bool
|
||||||
Admins []string
|
Admins []string
|
||||||
AdminAdd bool
|
AdminAdd bool
|
||||||
AdminSkip bool
|
AdminAddPlaylists bool
|
||||||
AdminVolume bool
|
AdminSkip bool
|
||||||
AdminMove bool
|
AdminVolume bool
|
||||||
AdminReload bool
|
AdminMove bool
|
||||||
AdminKill bool
|
AdminReload bool
|
||||||
|
AdminKill bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
122
playlist.go
Normal file
122
playlist.go
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* MumbleDJ
|
||||||
|
* By Matthieu Grieger
|
||||||
|
* playlist.go
|
||||||
|
* Copyright (c) 2014 Matthieu Grieger (MIT License)
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/jmoiron/jsonq"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Playlist type declaration.
|
||||||
|
type Playlist struct {
|
||||||
|
songs *SongQueue
|
||||||
|
youtubeId string
|
||||||
|
title string
|
||||||
|
submitter string
|
||||||
|
skippers []string
|
||||||
|
skipped bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new Playlist type. Before returning the new type, the playlist's metadata is collected
|
||||||
|
// via the YouTube Gdata API.
|
||||||
|
func NewPlaylist(user, id string) *Playlist {
|
||||||
|
queue := NewSongQueue()
|
||||||
|
jsonUrl := fmt.Sprintf("http://gdata.youtube.com/feeds/api/playlists/%s?v=2&alt=jsonc&maxresults=25", id)
|
||||||
|
jsonString := ""
|
||||||
|
|
||||||
|
if response, err := http.Get(jsonUrl); err == nil {
|
||||||
|
defer response.Body.Close()
|
||||||
|
if body, err := ioutil.ReadAll(response.Body); err == nil {
|
||||||
|
jsonString = string(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData := map[string]interface{}{}
|
||||||
|
decoder := json.NewDecoder(strings.NewReader(jsonString))
|
||||||
|
decoder.Decode(&jsonData)
|
||||||
|
jq := jsonq.NewQuery(jsonData)
|
||||||
|
|
||||||
|
playlistTitle, _ := jq.String("data", "title")
|
||||||
|
playlistItems, _ := jq.Int("data", "totalItems")
|
||||||
|
if playlistItems > 25 {
|
||||||
|
playlistItems = 25
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < playlistItems; i++ {
|
||||||
|
index := strconv.Itoa(i)
|
||||||
|
songTitle, _ := jq.String("data", "items", index, "video", "title")
|
||||||
|
songId, _ := jq.String("data", "items", index, "video", "id")
|
||||||
|
songThumbnail, _ := jq.String("data", "items", index, "video", "thumbnail", "hqDefault")
|
||||||
|
duration, _ := jq.Int("data", "items", index, "video", "duration")
|
||||||
|
songDuration := fmt.Sprintf("%d:%02d", duration/60, duration%60)
|
||||||
|
newSong := &Song{
|
||||||
|
submitter: user,
|
||||||
|
title: songTitle,
|
||||||
|
youtubeId: songId,
|
||||||
|
playlistId: id,
|
||||||
|
duration: songDuration,
|
||||||
|
thumbnailUrl: songThumbnail,
|
||||||
|
}
|
||||||
|
queue.AddItem(newSong)
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist := &Playlist{
|
||||||
|
songs: queue,
|
||||||
|
youtubeId: id,
|
||||||
|
title: playlistTitle,
|
||||||
|
submitter: user,
|
||||||
|
skipped: false,
|
||||||
|
}
|
||||||
|
return playlist
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (p *Playlist) AddSkip(username string) error {
|
||||||
|
for _, user := range p.skippers {
|
||||||
|
if username == user {
|
||||||
|
return errors.New("This user has already skipped the current song.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.skippers = append(p.skippers, username)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a skip from the skippers slice. If username is not in the slice, an error is
|
||||||
|
// returned.
|
||||||
|
func (p *Playlist) RemoveSkip(username string) error {
|
||||||
|
for i, user := range p.skippers {
|
||||||
|
if username == user {
|
||||||
|
p.skippers = append(p.skippers[:i], p.skippers[i+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 (p *Playlist) SkipReached(channelUsers int) bool {
|
||||||
|
if float32(len(p.skippers))/float32(channelUsers) >= dj.conf.General.PlaylistSkipRatio {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns "playlist" as the item type. Used for differentiating Songs from Playlists.
|
||||||
|
func (p *Playlist) ItemType() string {
|
||||||
|
return "playlist"
|
||||||
|
}
|
9
song.go
9
song.go
|
@ -24,8 +24,10 @@ type Song struct {
|
||||||
submitter string
|
submitter string
|
||||||
title string
|
title string
|
||||||
youtubeId string
|
youtubeId string
|
||||||
|
playlistId string
|
||||||
duration string
|
duration string
|
||||||
thumbnailUrl string
|
thumbnailUrl string
|
||||||
|
itemType string
|
||||||
skippers []string
|
skippers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,8 +58,10 @@ func NewSong(user, id string) *Song {
|
||||||
submitter: user,
|
submitter: user,
|
||||||
title: videoTitle,
|
title: videoTitle,
|
||||||
youtubeId: id,
|
youtubeId: id,
|
||||||
|
playlistId: "",
|
||||||
duration: videoDuration,
|
duration: videoDuration,
|
||||||
thumbnailUrl: videoThumbnail,
|
thumbnailUrl: videoThumbnail,
|
||||||
|
itemType: "song",
|
||||||
}
|
}
|
||||||
return song
|
return song
|
||||||
}
|
}
|
||||||
|
@ -127,3 +131,8 @@ func (s *Song) SkipReached(channelUsers int) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns "song" as the item type. Used for differentiating Songs from Playlists.
|
||||||
|
func (s *Song) ItemType() string {
|
||||||
|
return "song"
|
||||||
|
}
|
||||||
|
|
94
songqueue.go
94
songqueue.go
|
@ -11,38 +11,106 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// QueueItem type declaration. QueueItem is an interface that groups together Song and Playlist
|
||||||
|
// types in a queue.
|
||||||
|
type QueueItem interface {
|
||||||
|
AddSkip(string) error
|
||||||
|
RemoveSkip(string) error
|
||||||
|
SkipReached(int) bool
|
||||||
|
ItemType() string
|
||||||
|
}
|
||||||
|
|
||||||
// SongQueue type declaration. Serves as a wrapper around the queue structure defined in queue.go.
|
// SongQueue type declaration. Serves as a wrapper around the queue structure defined in queue.go.
|
||||||
type SongQueue struct {
|
type SongQueue struct {
|
||||||
queue []*Song
|
queue []QueueItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializes a new queue and returns the new SongQueue.
|
// Initializes a new queue and returns the new SongQueue.
|
||||||
func NewSongQueue() *SongQueue {
|
func NewSongQueue() *SongQueue {
|
||||||
return &SongQueue{
|
return &SongQueue{
|
||||||
queue: make([]*Song, 0),
|
queue: make([]QueueItem, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a song to the SongQueue.
|
// Adds an item to the SongQueue.
|
||||||
func (q *SongQueue) AddSong(s *Song) error {
|
func (q *SongQueue) AddItem(i QueueItem) error {
|
||||||
beforeLen := len(q.queue)
|
beforeLen := q.Len()
|
||||||
q.queue = append(q.queue, s)
|
q.queue = append(q.queue, i)
|
||||||
if len(q.queue) == beforeLen+1 {
|
if len(q.queue) == beforeLen+1 {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return errors.New("Could not add Song to the SongQueue.")
|
return errors.New("Could not add QueueItem to the SongQueue.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Moves to the next song in SongQueue. NextSong() pops the first value of the queue, and is stored
|
// Returns the current QueueItem.
|
||||||
// in dj.currentSong.
|
func (q *SongQueue) CurrentItem() QueueItem {
|
||||||
func (q *SongQueue) NextSong() *Song {
|
return q.queue[0]
|
||||||
s, queue := q.queue[0], q.queue[1:]
|
}
|
||||||
q.queue = queue
|
|
||||||
return s
|
// Moves to the next item in SongQueue. NextItem() removes the first value in the queue.
|
||||||
|
func (q *SongQueue) NextItem() {
|
||||||
|
q.queue = q.queue[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the length of the SongQueue.
|
// Returns the length of the SongQueue.
|
||||||
func (q *SongQueue) Len() int {
|
func (q *SongQueue) Len() int {
|
||||||
return len(q.queue)
|
return len(q.queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnItemFinished event. Deletes item that just finished playing, then queues the next item.
|
||||||
|
func (q *SongQueue) OnItemFinished() {
|
||||||
|
if q.CurrentItem().ItemType() == "playlist" {
|
||||||
|
if err := q.CurrentItem().(*Playlist).songs.CurrentItem().(*Song).Delete(); err == nil {
|
||||||
|
if q.CurrentItem().(*Playlist).skipped == true {
|
||||||
|
if q.Len() > 1 {
|
||||||
|
q.NextItem()
|
||||||
|
q.PrepareAndPlayNextItem()
|
||||||
|
} else {
|
||||||
|
q.queue = q.queue[1:]
|
||||||
|
}
|
||||||
|
} else if q.CurrentItem().(*Playlist).songs.Len() > 1 {
|
||||||
|
q.CurrentItem().(*Playlist).songs.NextItem()
|
||||||
|
q.PrepareAndPlayNextItem()
|
||||||
|
} else {
|
||||||
|
if q.Len() > 1 {
|
||||||
|
q.NextItem()
|
||||||
|
q.PrepareAndPlayNextItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := q.CurrentItem().(*Song).Delete(); err == nil {
|
||||||
|
if q.Len() > 1 {
|
||||||
|
q.NextItem()
|
||||||
|
q.PrepareAndPlayNextItem()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *SongQueue) PrepareAndPlayNextItem() {
|
||||||
|
if q.CurrentItem().ItemType() == "playlist" {
|
||||||
|
if err := q.CurrentItem().(*Playlist).songs.CurrentItem().(*Song).Download(); err == nil {
|
||||||
|
q.CurrentItem().(*Playlist).songs.CurrentItem().(*Song).Play()
|
||||||
|
} else {
|
||||||
|
username := q.CurrentItem().(*Playlist).submitter
|
||||||
|
user := dj.client.Self().Channel().Users().Find(username)
|
||||||
|
user.Send(AUDIO_FAIL_MSG)
|
||||||
|
q.OnItemFinished()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := q.CurrentItem().(*Song).Download(); err == nil {
|
||||||
|
q.CurrentItem().(*Song).Play()
|
||||||
|
} else {
|
||||||
|
username := q.CurrentItem().(*Song).submitter
|
||||||
|
user := dj.client.Self().Channel().Users().Find(username)
|
||||||
|
user.Send(AUDIO_FAIL_MSG)
|
||||||
|
q.OnItemFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
31
strings.go
31
strings.go
|
@ -10,6 +10,9 @@ package main
|
||||||
// Message shown to users when they do not have permission to execute a command.
|
// Message shown to users when they do not have permission to execute a command.
|
||||||
const NO_PERMISSION_MSG = "You do not have permission to execute that command."
|
const NO_PERMISSION_MSG = "You do not have permission to execute that command."
|
||||||
|
|
||||||
|
// Message shown to users when they try to add a playlist to the queue and do not have permission to do so.
|
||||||
|
const NO_PLAYLIST_PERMISSION_MSG = "You do not have permission to add playlists to the queue."
|
||||||
|
|
||||||
// Message shown to users when they try to execute a command that doesn't exist.
|
// Message shown to users when they try to execute a command that doesn't exist.
|
||||||
const COMMAND_DOESNT_EXIST_MSG = "The command you entered does not exist."
|
const COMMAND_DOESNT_EXIST_MSG = "The command you entered does not exist."
|
||||||
|
|
||||||
|
@ -23,6 +26,9 @@ const INVALID_URL_MSG = "The URL you submitted does not match the required forma
|
||||||
// no song is playing.
|
// no song is playing.
|
||||||
const NO_MUSIC_PLAYING_MSG = "There is no music playing at the moment."
|
const NO_MUSIC_PLAYING_MSG = "There is no music playing at the moment."
|
||||||
|
|
||||||
|
// Message shown to users when they attempt to skip a playlist when there is no playlist playing.
|
||||||
|
const NO_PLAYLIST_PLAYING_MSG = "There is no playlist playing at the moment."
|
||||||
|
|
||||||
// Message shown to users when they issue a command that requires an argument and one was not supplied.
|
// Message shown to users when they issue a command that requires an argument and one was not supplied.
|
||||||
const NO_ARGUMENT_MSG = "The command you issued requires an argument and you did not provide one."
|
const NO_ARGUMENT_MSG = "The command you issued requires an argument and you did not provide one."
|
||||||
|
|
||||||
|
@ -32,11 +38,11 @@ const NOT_IN_VOLUME_RANGE_MSG = "Out of range. The volume must be between %f and
|
||||||
// Message shown to user when a successful configuration reload finishes.
|
// Message shown to user when a successful configuration reload finishes.
|
||||||
const CONFIG_RELOAD_SUCCESS_MSG = "The configuration has been successfully reloaded."
|
const CONFIG_RELOAD_SUCCESS_MSG = "The configuration has been successfully reloaded."
|
||||||
|
|
||||||
// Message shown to user when an admin skips a song.
|
// Message shown to users when an admin skips a song.
|
||||||
const ADMIN_SONG_SKIP_MSG = "An admin has decided to skip the current song."
|
const ADMIN_SONG_SKIP_MSG = "An admin has decided to skip the current song."
|
||||||
|
|
||||||
// Message shown to user when the kill command errors.
|
// Message shown to users when an admin skips a playlist.
|
||||||
const KILL_ERROR_MSG = "An error occurred while attempting to kill the bot."
|
const ADMIN_PLAYLIST_SKIP_MSG = "An admin has decided to skip the current playlist."
|
||||||
|
|
||||||
// Message shown to users when the audio for a video could not be downloaded.
|
// Message shown to users when the audio for a video could not be downloaded.
|
||||||
const AUDIO_FAIL_MSG = "The audio download for this video failed. YouTube has likely not generated the audio files for this video yet."
|
const AUDIO_FAIL_MSG = "The audio download for this video failed. YouTube has likely not generated the audio files for this video yet."
|
||||||
|
@ -61,14 +67,24 @@ const SONG_ADDED_HTML = `
|
||||||
<b>%s</b> has added "%s" to the queue.
|
<b>%s</b> has added "%s" to the queue.
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// Message shown to channel when a playlist is added to the queue by a user.
|
||||||
|
const PLAYLIST_ADDED_HTML = `
|
||||||
|
<b>%s</b> has added the playlist "%s" to the queue.
|
||||||
|
`
|
||||||
|
|
||||||
// Message shown to channel when a song has been skipped.
|
// Message shown to channel when a song has been skipped.
|
||||||
const SONG_SKIPPED_HTML = `
|
const SONG_SKIPPED_HTML = `
|
||||||
The number of votes required for a skip has been met. <b>Skipping song!</b>
|
The number of votes required for a skip has been met. <b>Skipping song!</b>
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// Message shown to channel when a playlist has been skipped.
|
||||||
|
const PLAYLIST_SKIPPED_HTML = `
|
||||||
|
The number of votes required for a skip has been met. <b>Skipping playlist!</b>
|
||||||
|
`
|
||||||
|
|
||||||
// Message shown to users when they ask for the current volume (volume command without argument)
|
// Message shown to users when they ask for the current volume (volume command without argument)
|
||||||
const CUR_VOLUME_HTML = `
|
const CUR_VOLUME_HTML = `
|
||||||
The current volume is <b>%f</b>.
|
The current volume is <b>%.2f</b>.
|
||||||
`
|
`
|
||||||
|
|
||||||
// Message shown to users when another user votes to skip the current song.
|
// Message shown to users when another user votes to skip the current song.
|
||||||
|
@ -76,7 +92,12 @@ const SKIP_ADDED_HTML = `
|
||||||
<b>%s</b> has voted to skip the current song.
|
<b>%s</b> has voted to skip the current song.
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// Message shown to users when another user votes to skip the current playlist.
|
||||||
|
const PLAYLIST_SKIP_ADDED_HTML = `
|
||||||
|
<b>%s</b> has voted to skip the current playlist.
|
||||||
|
`
|
||||||
|
|
||||||
// Message shown to users when they successfully change the volume.
|
// Message shown to users when they successfully change the volume.
|
||||||
const VOLUME_SUCCESS_HTML = `
|
const VOLUME_SUCCESS_HTML = `
|
||||||
<b>%s</b> has changed the volume to <b>%s</b>.
|
<b>%s</b> has changed the volume to <b>%.2f</b>.
|
||||||
`
|
`
|
||||||
|
|
Reference in a new issue