Song conditions are now checked in Service
This commit is contained in:
parent
edba60939a
commit
c122a1f8e4
72
service.go
72
service.go
|
@ -11,14 +11,17 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/layeh/gumble/gumble"
|
"github.com/layeh/gumble/gumble"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service interface. Each service will implement these functions
|
// Service interface. Each service will implement these functions
|
||||||
type Service interface {
|
type Service interface {
|
||||||
|
ServiceName() string
|
||||||
|
TrackName() string
|
||||||
URLRegex(string) bool
|
URLRegex(string) bool
|
||||||
NewRequest(*gumble.User, string) (string, error)
|
NewRequest(*gumble.User, string) ([]Song, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Song interface. Each service will implement these
|
// Song interface. Each service will implement these
|
||||||
|
@ -34,7 +37,7 @@ type Song interface {
|
||||||
Title() string
|
Title() string
|
||||||
ID() string
|
ID() string
|
||||||
Filename() string
|
Filename() string
|
||||||
Duration() string
|
Duration() time.Duration
|
||||||
Thumbnail() string
|
Thumbnail() string
|
||||||
Playlist() Playlist
|
Playlist() Playlist
|
||||||
DontSkip() bool
|
DontSkip() bool
|
||||||
|
@ -69,27 +72,58 @@ func FindServiceAndAdd(user *gumble.User, url string) error {
|
||||||
if urlService == nil {
|
if urlService == nil {
|
||||||
return errors.New(INVALID_URL_MSG)
|
return errors.New(INVALID_URL_MSG)
|
||||||
} else {
|
} else {
|
||||||
oldLength := dj.queue.Len()
|
|
||||||
var title string
|
var title string
|
||||||
|
var songsAdded = 0
|
||||||
|
var songArray []Song
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if title, err = urlService.NewRequest(user, url); err == nil {
|
// Get service to create songs
|
||||||
dj.client.Self.Channel.Send(fmt.Sprintf(SONG_ADDED_HTML, user.Name, title), false)
|
if songArray, err = urlService.NewRequest(user, url); err != nil {
|
||||||
|
return err
|
||||||
// Starts playing the new song if nothing else is playing
|
|
||||||
if oldLength == 0 && dj.queue.Len() != 0 && !dj.audioStream.IsPlaying() {
|
|
||||||
if err := dj.queue.CurrentSong().Download(); err == nil {
|
|
||||||
dj.queue.CurrentSong().Play()
|
|
||||||
} else {
|
|
||||||
dj.queue.CurrentSong().Delete()
|
|
||||||
dj.queue.OnSongFinished()
|
|
||||||
return errors.New("FAILED_TO_DOWNLOAD")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dj.SendPrivateMessage(user, err.Error())
|
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
// Check Playlist Permission
|
||||||
|
if len(songArray) > 1 && !dj.HasPermission(user.Name, dj.conf.Permissions.AdminAddPlaylists) {
|
||||||
|
return errors.New(NO_PLAYLIST_PERMISSION_MSG)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through all songs and add to the queue
|
||||||
|
oldLength := dj.queue.Len()
|
||||||
|
for _, song := range songArray {
|
||||||
|
// Check song is not too long
|
||||||
|
if dj.conf.General.MaxSongDuration == 0 || int(song.Duration().Seconds()) <= dj.conf.General.MaxSongDuration {
|
||||||
|
if !isNil(song.Playlist()) {
|
||||||
|
title = song.Playlist().Title()
|
||||||
|
} else {
|
||||||
|
title = song.Title()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add song to queue
|
||||||
|
dj.queue.AddSong(song)
|
||||||
|
songsAdded++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert channel of added song/playlist
|
||||||
|
if songsAdded == 0 {
|
||||||
|
return errors.New(fmt.Sprintf(TRACK_TOO_LONG_MSG, urlService.ServiceName()))
|
||||||
|
} else if songsAdded == 1 {
|
||||||
|
dj.client.Self.Channel.Send(fmt.Sprintf(SONG_ADDED_HTML, user.Name, title), false)
|
||||||
|
} else {
|
||||||
|
dj.client.Self.Channel.Send(fmt.Sprintf(PLAYLIST_ADDED_HTML, user.Name, title), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts playing the new song if nothing else is playing
|
||||||
|
if oldLength == 0 && dj.queue.Len() != 0 && !dj.audioStream.IsPlaying() {
|
||||||
|
if err := dj.queue.CurrentSong().Download(); err == nil {
|
||||||
|
dj.queue.CurrentSong().Play()
|
||||||
|
} else {
|
||||||
|
dj.queue.CurrentSong().Delete()
|
||||||
|
dj.queue.OnSongFinished()
|
||||||
|
return errors.New(AUDIO_FAIL_MSG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jmoiron/jsonq"
|
"github.com/jmoiron/jsonq"
|
||||||
"github.com/layeh/gumble/gumble"
|
"github.com/layeh/gumble/gumble"
|
||||||
|
@ -30,42 +29,53 @@ type SoundCloud struct{}
|
||||||
// SOUNDCLOUD SERVICE
|
// SOUNDCLOUD SERVICE
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|
||||||
|
// ServiceName is the human readable version of the service name
|
||||||
|
func (sc SoundCloud) ServiceName() string {
|
||||||
|
return "Soundcloud"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackName is the human readable version of the service name
|
||||||
|
func (sc SoundCloud) TrackName() string {
|
||||||
|
return "Song"
|
||||||
|
}
|
||||||
|
|
||||||
// URLRegex checks to see if service will accept URL
|
// URLRegex checks to see if service will accept URL
|
||||||
func (sc SoundCloud) URLRegex(url string) bool {
|
func (sc SoundCloud) URLRegex(url string) bool {
|
||||||
return RegexpFromURL(url, []string{soundcloudSongPattern, soundcloudPlaylistPattern}) != nil
|
return RegexpFromURL(url, []string{soundcloudSongPattern, soundcloudPlaylistPattern}) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest creates the requested song/playlist and adds to the queue
|
// NewRequest creates the requested song/playlist and adds to the queue
|
||||||
func (sc SoundCloud) NewRequest(user *gumble.User, url string) (string, error) {
|
func (sc SoundCloud) NewRequest(user *gumble.User, url string) ([]Song, error) {
|
||||||
var apiResponse *jsonq.JsonQuery
|
var apiResponse *jsonq.JsonQuery
|
||||||
|
var songArray []Song
|
||||||
var err error
|
var err error
|
||||||
timesplit := strings.Split(url, "#t=")
|
timesplit := strings.Split(url, "#t=")
|
||||||
url = fmt.Sprintf("http://api.soundcloud.com/resolve?url=%s&client_id=%s", timesplit[0], os.Getenv("SOUNDCLOUD_API_KEY"))
|
url = fmt.Sprintf("http://api.soundcloud.com/resolve?url=%s&client_id=%s", timesplit[0], os.Getenv("SOUNDCLOUD_API_KEY"))
|
||||||
if apiResponse, err = PerformGetRequest(url); err != nil {
|
if apiResponse, err = PerformGetRequest(url); err != nil {
|
||||||
return "", errors.New(INVALID_API_KEY)
|
return nil, errors.New(fmt.Sprintf(INVALID_API_KEY, sc.ServiceName()))
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks, err := apiResponse.ArrayOfObjects("tracks")
|
tracks, err := apiResponse.ArrayOfObjects("tracks")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// PLAYLIST
|
// PLAYLIST
|
||||||
if dj.HasPermission(user.Name, dj.conf.Permissions.AdminAddPlaylists) {
|
// Create playlist
|
||||||
// Create playlist
|
title, _ := apiResponse.String("title")
|
||||||
title, _ := apiResponse.String("title")
|
permalink, _ := apiResponse.String("permalink_url")
|
||||||
permalink, _ := apiResponse.String("permalink_url")
|
playlist := &YouTubePlaylist{
|
||||||
playlist := &YouTubePlaylist{
|
id: permalink,
|
||||||
id: permalink,
|
title: title,
|
||||||
title: title,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all tracks
|
|
||||||
for _, t := range tracks {
|
|
||||||
sc.NewSong(user, jsonq.NewQuery(t), 0, playlist)
|
|
||||||
}
|
|
||||||
return playlist.Title(), nil
|
|
||||||
}
|
}
|
||||||
return "", errors.New(NO_PLAYLIST_PERMISSION_MSG)
|
|
||||||
|
// Add all tracks
|
||||||
|
for _, t := range tracks {
|
||||||
|
if song, err := sc.NewSong(user, jsonq.NewQuery(t), 0, playlist); err == nil {
|
||||||
|
songArray = append(songArray, song)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return songArray, nil
|
||||||
} else {
|
} else {
|
||||||
// SONG
|
// SONG
|
||||||
|
// Calculate offset
|
||||||
offset := 0
|
offset := 0
|
||||||
if len(timesplit) == 2 {
|
if len(timesplit) == 2 {
|
||||||
timesplit = strings.Split(timesplit[1], ":")
|
timesplit = strings.Split(timesplit[1], ":")
|
||||||
|
@ -76,12 +86,17 @@ func (sc SoundCloud) NewRequest(user *gumble.User, url string) (string, error) {
|
||||||
multiplier *= 60
|
multiplier *= 60
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sc.NewSong(user, apiResponse, offset, nil)
|
|
||||||
|
// Add the track
|
||||||
|
if song, err := sc.NewSong(user, apiResponse, offset, nil); err == nil {
|
||||||
|
return append(songArray, song), err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSong creates a track and adds to the queue
|
// NewSong creates a track and adds to the queue
|
||||||
func (sc SoundCloud) NewSong(user *gumble.User, trackData *jsonq.JsonQuery, offset int, playlist Playlist) (string, error) {
|
func (sc SoundCloud) NewSong(user *gumble.User, trackData *jsonq.JsonQuery, offset int, playlist Playlist) (Song, error) {
|
||||||
title, _ := trackData.String("title")
|
title, _ := trackData.String("title")
|
||||||
id, _ := trackData.Int("id")
|
id, _ := trackData.Int("id")
|
||||||
durationMS, _ := trackData.Int("duration")
|
durationMS, _ := trackData.Int("duration")
|
||||||
|
@ -93,26 +108,19 @@ func (sc SoundCloud) NewSong(user *gumble.User, trackData *jsonq.JsonQuery, offs
|
||||||
thumbnail, _ = jsonq.NewQuery(userObj).String("avatar_url")
|
thumbnail, _ = jsonq.NewQuery(userObj).String("avatar_url")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check song is not longer than the MaxSongDuration
|
song := &YouTubeSong{
|
||||||
if dj.conf.General.MaxSongDuration == 0 || (durationMS/1000) <= dj.conf.General.MaxSongDuration {
|
id: strconv.Itoa(id),
|
||||||
timeDuration, _ := time.ParseDuration(strconv.Itoa(durationMS/1000) + "s")
|
title: title,
|
||||||
duration := strings.NewReplacer("h", ":", "m", ":", "s", "").Replace(timeDuration.String())
|
url: url,
|
||||||
|
thumbnail: thumbnail,
|
||||||
song := &YouTubeSong{
|
submitter: user,
|
||||||
id: strconv.Itoa(id),
|
duration: durationMS / 1000,
|
||||||
title: title,
|
offset: offset,
|
||||||
url: url,
|
format: "mp3",
|
||||||
thumbnail: thumbnail,
|
playlist: playlist,
|
||||||
submitter: user,
|
skippers: make([]string, 0),
|
||||||
duration: duration,
|
dontSkip: false,
|
||||||
offset: offset,
|
service: sc,
|
||||||
format: "mp3",
|
|
||||||
playlist: playlist,
|
|
||||||
skippers: make([]string, 0),
|
|
||||||
dontSkip: false,
|
|
||||||
}
|
|
||||||
dj.queue.AddSong(song)
|
|
||||||
return song.Title(), nil
|
|
||||||
}
|
}
|
||||||
return "", errors.New(VIDEO_TOO_LONG_MSG)
|
return song, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,15 +8,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/jsonq"
|
"github.com/jmoiron/jsonq"
|
||||||
"github.com/layeh/gumble/gumble"
|
"github.com/layeh/gumble/gumble"
|
||||||
|
@ -35,23 +33,29 @@ var youtubeVideoPatterns = []string{
|
||||||
// YouTube implements the Service interface
|
// YouTube implements the Service interface
|
||||||
type YouTube struct{}
|
type YouTube struct{}
|
||||||
|
|
||||||
|
// ServiceName is the human readable version of the service name
|
||||||
|
func (yt YouTube) ServiceName() string {
|
||||||
|
return "YouTube"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackName is the human readable version of the service name
|
||||||
|
func (yt YouTube) TrackName() string {
|
||||||
|
return "Video"
|
||||||
|
}
|
||||||
|
|
||||||
// URLRegex checks to see if service will accept URL
|
// URLRegex checks to see if service will accept URL
|
||||||
func (yt YouTube) URLRegex(url string) bool {
|
func (yt YouTube) URLRegex(url string) bool {
|
||||||
return RegexpFromURL(url, append(youtubeVideoPatterns, []string{youtubePlaylistPattern}...)) != nil
|
return RegexpFromURL(url, append(youtubeVideoPatterns, []string{youtubePlaylistPattern}...)) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest creates the requested song/playlist and adds to the queue
|
// NewRequest creates the requested song/playlist and adds to the queue
|
||||||
func (yt YouTube) NewRequest(user *gumble.User, url string) (string, error) {
|
func (yt YouTube) NewRequest(user *gumble.User, url string) ([]Song, error) {
|
||||||
|
var songArray []Song
|
||||||
var shortURL, startOffset = "", ""
|
var shortURL, startOffset = "", ""
|
||||||
if re, err := regexp.Compile(youtubePlaylistPattern); err == nil {
|
if re, err := regexp.Compile(youtubePlaylistPattern); err == nil {
|
||||||
if re.MatchString(url) {
|
if re.MatchString(url) {
|
||||||
if dj.HasPermission(user.Name, dj.conf.Permissions.AdminAddPlaylists) {
|
shortURL = re.FindStringSubmatch(url)[1]
|
||||||
shortURL = re.FindStringSubmatch(url)[1]
|
return yt.NewPlaylist(user, shortURL)
|
||||||
playlist, err := yt.NewPlaylist(user, shortURL)
|
|
||||||
return playlist.Title(), err
|
|
||||||
} else {
|
|
||||||
return "", errors.New("NO_PLAYLIST_PERMISSION")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
re = RegexpFromURL(url, youtubeVideoPatterns)
|
re = RegexpFromURL(url, youtubeVideoPatterns)
|
||||||
matches := re.FindAllStringSubmatch(url, -1)
|
matches := re.FindAllStringSubmatch(url, -1)
|
||||||
|
@ -61,105 +65,42 @@ func (yt YouTube) NewRequest(user *gumble.User, url string) (string, error) {
|
||||||
}
|
}
|
||||||
song, err := yt.NewSong(user.Name, shortURL, startOffset, nil)
|
song, err := yt.NewSong(user.Name, shortURL, startOffset, nil)
|
||||||
if !isNil(song) {
|
if !isNil(song) {
|
||||||
|
<<<<<<< HEAD
|
||||||
return song.Title(), err
|
return song.Title(), err
|
||||||
|
=======
|
||||||
|
return append(songArray, song), nil
|
||||||
|
>>>>>>> abf98ad... Song conditions are now checked in Service
|
||||||
} else {
|
} else {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSong gathers the metadata for a song extracted from a YouTube video, and returns the song.
|
// NewSong gathers the metadata for a song extracted from a YouTube video, and returns the song.
|
||||||
func (yt YouTube) NewSong(user *gumble.User, id, offset string, playlist Playlist) (Song, error) {
|
func (yt YouTube) NewSong(user *gumble.User, id, offset string, playlist Playlist) (Song, error) {
|
||||||
var apiResponse *jsonq.JsonQuery
|
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%s&key=%s", id, os.Getenv("YOUTUBE_API_KEY"))
|
||||||
var err error
|
if apiResponse, err := PerformGetRequest(url); err == nil {
|
||||||
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%s&key=%s",
|
title, _ := apiResponse.String("items", "0", "snippet", "title")
|
||||||
id, os.Getenv("YOUTUBE_API_KEY"))
|
thumbnail, _ := apiResponse.String("items", "0", "snippet", "thumbnails", "high", "url")
|
||||||
if apiResponse, err = PerformGetRequest(url); err != nil {
|
duration, _ := apiResponse.String("items", "0", "contentDetails", "duration")
|
||||||
return nil, errors.New(INVALID_API_KEY)
|
|
||||||
}
|
|
||||||
|
|
||||||
var offsetDays, offsetHours, offsetMinutes, offsetSeconds int64
|
|
||||||
if offset != "" {
|
|
||||||
offsetExp := regexp.MustCompile(`t\=(?P<days>\d+d)?(?P<hours>\d+h)?(?P<minutes>\d+m)?(?P<seconds>\d+s)?`)
|
|
||||||
offsetMatch := offsetExp.FindStringSubmatch(offset)
|
|
||||||
offsetResult := make(map[string]string)
|
|
||||||
for i, name := range offsetExp.SubexpNames() {
|
|
||||||
if i < len(offsetMatch) {
|
|
||||||
offsetResult[name] = offsetMatch[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if offsetResult["days"] != "" {
|
|
||||||
offsetDays, _ = strconv.ParseInt(strings.TrimSuffix(offsetResult["days"], "d"), 10, 32)
|
|
||||||
}
|
|
||||||
if offsetResult["hours"] != "" {
|
|
||||||
offsetHours, _ = strconv.ParseInt(strings.TrimSuffix(offsetResult["hours"], "h"), 10, 32)
|
|
||||||
}
|
|
||||||
if offsetResult["minutes"] != "" {
|
|
||||||
offsetMinutes, _ = strconv.ParseInt(strings.TrimSuffix(offsetResult["minutes"], "m"), 10, 32)
|
|
||||||
}
|
|
||||||
if offsetResult["seconds"] != "" {
|
|
||||||
offsetSeconds, _ = strconv.ParseInt(strings.TrimSuffix(offsetResult["seconds"], "s"), 10, 32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
title, _ := apiResponse.String("items", "0", "snippet", "title")
|
|
||||||
thumbnail, _ := apiResponse.String("items", "0", "snippet", "thumbnails", "high", "url")
|
|
||||||
duration, _ := apiResponse.String("items", "0", "contentDetails", "duration")
|
|
||||||
|
|
||||||
var days, hours, minutes, seconds int64
|
|
||||||
timestampExp := regexp.MustCompile(`P(?P<days>\d+D)?T(?P<hours>\d+H)?(?P<minutes>\d+M)?(?P<seconds>\d+S)?`)
|
|
||||||
timestampMatch := timestampExp.FindStringSubmatch(duration)
|
|
||||||
timestampResult := make(map[string]string)
|
|
||||||
for i, name := range timestampExp.SubexpNames() {
|
|
||||||
if i < len(timestampMatch) {
|
|
||||||
timestampResult[name] = timestampMatch[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if timestampResult["days"] != "" {
|
|
||||||
days, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["days"], "D"), 10, 32)
|
|
||||||
}
|
|
||||||
if timestampResult["hours"] != "" {
|
|
||||||
hours, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["hours"], "H"), 10, 32)
|
|
||||||
}
|
|
||||||
if timestampResult["minutes"] != "" {
|
|
||||||
minutes, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["minutes"], "M"), 10, 32)
|
|
||||||
}
|
|
||||||
if timestampResult["seconds"] != "" {
|
|
||||||
seconds, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["seconds"], "S"), 10, 32)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalSeconds := int((days * 86400) + (hours * 3600) + (minutes * 60) + seconds)
|
|
||||||
var durationString string
|
|
||||||
if hours != 0 {
|
|
||||||
if days != 0 {
|
|
||||||
durationString = fmt.Sprintf("%d:%02d:%02d:%02d", days, hours, minutes, seconds)
|
|
||||||
} else {
|
|
||||||
durationString = fmt.Sprintf("%d:%02d:%02d", hours, minutes, seconds)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
durationString = fmt.Sprintf("%d:%02d", minutes, seconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dj.conf.General.MaxSongDuration == 0 || totalSeconds <= dj.conf.General.MaxSongDuration {
|
|
||||||
song := &YouTubeSong{
|
song := &YouTubeSong{
|
||||||
submitter: user,
|
submitter: user,
|
||||||
title: title,
|
title: title,
|
||||||
id: id,
|
id: id,
|
||||||
url: "https://youtu.be/" + id,
|
url: "https://youtu.be/" + id,
|
||||||
offset: int((offsetDays * 86400) + (offsetHours * 3600) + (offsetMinutes * 60) + offsetSeconds),
|
offset: int(yt.parseTime(offset, `\?T\=(?P<days>\d+D)?(?P<hours>\d+H)?(?P<minutes>\d+M)?(?P<seconds>\d+S)?`).Seconds()),
|
||||||
duration: durationString,
|
duration: int(yt.parseTime(duration, `P(?P<days>\d+D)?T(?P<hours>\d+H)?(?P<minutes>\d+M)?(?P<seconds>\d+S)?`).Seconds()),
|
||||||
thumbnail: thumbnail,
|
thumbnail: thumbnail,
|
||||||
format: "m4a",
|
format: "m4a",
|
||||||
skippers: make([]string, 0),
|
skippers: make([]string, 0),
|
||||||
playlist: playlist,
|
playlist: playlist,
|
||||||
dontSkip: false,
|
dontSkip: false,
|
||||||
|
service: yt,
|
||||||
}
|
}
|
||||||
dj.queue.AddSong(song)
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
return song, nil
|
return song, nil
|
||||||
|
@ -281,12 +222,47 @@ func (s *YouTubeSong) SkipReached(channelUsers int) bool {
|
||||||
return true
|
return true
|
||||||
>>>>>>> 2df3613... Fixed cache clearing earlier than expected
|
>>>>>>> 2df3613... Fixed cache clearing earlier than expected
|
||||||
}
|
}
|
||||||
return nil, errors.New(VIDEO_TOO_LONG_MSG)
|
return nil, errors.New(fmt.Sprintf(INVALID_API_KEY, yt.ServiceName()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTime converts from the string youtube returns to a time.Duration
|
||||||
|
func (yt YouTube) parseTime(duration, regex string) time.Duration {
|
||||||
|
var days, hours, minutes, seconds, totalSeconds int64
|
||||||
|
if duration != "" {
|
||||||
|
timestampExp := regexp.MustCompile(regex)
|
||||||
|
timestampMatch := timestampExp.FindStringSubmatch(strings.ToUpper(duration))
|
||||||
|
timestampResult := make(map[string]string)
|
||||||
|
for i, name := range timestampExp.SubexpNames() {
|
||||||
|
if i < len(timestampMatch) {
|
||||||
|
timestampResult[name] = timestampMatch[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if timestampResult["days"] != "" {
|
||||||
|
days, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["days"], "D"), 10, 32)
|
||||||
|
}
|
||||||
|
if timestampResult["hours"] != "" {
|
||||||
|
hours, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["hours"], "H"), 10, 32)
|
||||||
|
}
|
||||||
|
if timestampResult["minutes"] != "" {
|
||||||
|
minutes, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["minutes"], "M"), 10, 32)
|
||||||
|
}
|
||||||
|
if timestampResult["seconds"] != "" {
|
||||||
|
seconds, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["seconds"], "S"), 10, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalSeconds = int64((days * 86400) + (hours * 3600) + (minutes * 60) + seconds)
|
||||||
|
} else {
|
||||||
|
totalSeconds = 0
|
||||||
|
}
|
||||||
|
output, _ := time.ParseDuration(strconv.Itoa(int(totalSeconds)) + "s")
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlaylist gathers the metadata for a YouTube playlist and returns it.
|
// NewPlaylist gathers the metadata for a YouTube playlist and returns it.
|
||||||
func (yt YouTube) NewPlaylist(user *gumble.User, id string) (Playlist, error) {
|
func (yt YouTube) NewPlaylist(user *gumble.User, id string) ([]Song, error) {
|
||||||
var apiResponse *jsonq.JsonQuery
|
var apiResponse *jsonq.JsonQuery
|
||||||
|
var songArray []Song
|
||||||
var err error
|
var err error
|
||||||
// Retrieve title of playlist
|
// Retrieve title of playlist
|
||||||
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/playlists?part=snippet&id=%s&key=%s", id, os.Getenv("YOUTUBE_API_KEY"))
|
url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/playlists?part=snippet&id=%s&key=%s", id, os.Getenv("YOUTUBE_API_KEY"))
|
||||||
|
@ -314,35 +290,9 @@ func (yt YouTube) NewPlaylist(user *gumble.User, id string) (Playlist, error) {
|
||||||
for i := 0; i < numVideos; i++ {
|
for i := 0; i < numVideos; i++ {
|
||||||
index := strconv.Itoa(i)
|
index := strconv.Itoa(i)
|
||||||
videoID, _ := apiResponse.String("items", index, "snippet", "resourceId", "videoId")
|
videoID, _ := apiResponse.String("items", index, "snippet", "resourceId", "videoId")
|
||||||
yt.NewSong(user, videoID, "", playlist)
|
if song, err := yt.NewSong(user, videoID, "", playlist); err == nil {
|
||||||
}
|
songArray = append(songArray, song)
|
||||||
return playlist, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PerformGetRequest does all the grunt work for a YouTube HTTPS GET request.
|
|
||||||
func PerformGetRequest(url string) (*jsonq.JsonQuery, error) {
|
|
||||||
jsonString := ""
|
|
||||||
|
|
||||||
if response, err := http.Get(url); err == nil {
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode == 200 {
|
|
||||||
if body, err := ioutil.ReadAll(response.Body); err == nil {
|
|
||||||
jsonString = string(body)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if response.StatusCode == 403 {
|
|
||||||
return nil, errors.New("Invalid API key supplied.")
|
|
||||||
}
|
|
||||||
return nil, errors.New("Invalid ID supplied.")
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return nil, errors.New("An error occurred while receiving HTTP GET response.")
|
|
||||||
}
|
}
|
||||||
|
return songArray, nil
|
||||||
jsonData := map[string]interface{}{}
|
|
||||||
decoder := json.NewDecoder(strings.NewReader(jsonString))
|
|
||||||
decoder.Decode(&jsonData)
|
|
||||||
jq := jsonq.NewQuery(jsonData)
|
|
||||||
|
|
||||||
return jq, nil
|
|
||||||
}
|
}
|
||||||
|
|
16
strings.go
16
strings.go
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
// Message shown to users when the bot has an invalid YouTube API key.
|
// Message shown to users when the bot has an invalid API key.
|
||||||
const INVALID_API_KEY = "MumbleDJ does not have a valid YouTube API key."
|
const INVALID_API_KEY = "MumbleDJ does not have a valid %s API key."
|
||||||
|
|
||||||
// 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."
|
||||||
|
@ -26,7 +26,7 @@ const CHANNEL_DOES_NOT_EXIST_MSG = "The channel you specified does not exist."
|
||||||
const INVALID_URL_MSG = "The URL you submitted does not match the required format."
|
const INVALID_URL_MSG = "The URL you submitted does not match the required format."
|
||||||
|
|
||||||
// Message shown to users when they attempt to add a video that's too long
|
// Message shown to users when they attempt to add a video that's too long
|
||||||
const VIDEO_TOO_LONG_MSG = "The video you submitted exceeds the duration allowed by the server."
|
const TRACK_TOO_LONG_MSG = "The %s you submitted exceeds the duration allowed by the server."
|
||||||
|
|
||||||
// Message shown to users when they attempt to perform an action on a song when
|
// Message shown to users when they attempt to perform an action on a song when
|
||||||
// no song is playing.
|
// no song is playing.
|
||||||
|
@ -54,10 +54,10 @@ const ADMIN_SONG_SKIP_MSG = "An admin has decided to skip the current song."
|
||||||
const ADMIN_PLAYLIST_SKIP_MSG = "An admin has decided to skip the current playlist."
|
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. Skipping to the next song!"
|
const AUDIO_FAIL_MSG = "The audio download for this video failed. %s has likely not generated the audio files for this %s yet. Skipping to the next song!"
|
||||||
|
|
||||||
// Message shown to users when they supply a YouTube URL that does not contain a valid ID.
|
// Message shown to users when they supply an URL that does not contain a valid ID.
|
||||||
const INVALID_YOUTUBE_ID_MSG = "The YouTube URL you supplied did not contain a valid YouTube ID."
|
const INVALID_ID_MSG = "The %s URL you supplied did not contain a valid ID."
|
||||||
|
|
||||||
// Message shown to user when they successfully update the bot's comment.
|
// Message shown to user when they successfully update the bot's comment.
|
||||||
const COMMENT_UPDATED_MSG = "The comment for the bot has successfully been updated."
|
const COMMENT_UPDATED_MSG = "The comment for the bot has successfully been updated."
|
||||||
|
@ -95,7 +95,7 @@ const PLAYLIST_SKIPPED_HTML = `
|
||||||
const HELP_HTML = `<br/>
|
const HELP_HTML = `<br/>
|
||||||
<b>User Commands:</b>
|
<b>User Commands:</b>
|
||||||
<p><b>!help</b> - Displays this help.</p>
|
<p><b>!help</b> - Displays this help.</p>
|
||||||
<p><b>!add</b> - Adds songs to queue.</p>
|
<p><b>!add</b> - Adds songs/playlists to queue.</p>
|
||||||
<p><b>!volume</b> - Either tells you the current volume or sets it to a new volume.</p>
|
<p><b>!volume</b> - Either tells you the current volume or sets it to a new volume.</p>
|
||||||
<p><b>!skip</b> - Casts a vote to skip the current song</p>
|
<p><b>!skip</b> - Casts a vote to skip the current song</p>
|
||||||
<p> <b>!skipplaylist</b> - Casts a vote to skip over the current playlist.</p>
|
<p> <b>!skipplaylist</b> - Casts a vote to skip over the current playlist.</p>
|
||||||
|
@ -168,5 +168,5 @@ const CURRENT_SONG_HTML = `
|
||||||
// Message shown to users when the currentsong command is issued when a song from a
|
// Message shown to users when the currentsong command is issued when a song from a
|
||||||
// playlist is playing.
|
// playlist is playing.
|
||||||
const CURRENT_SONG_PLAYLIST_HTML = `
|
const CURRENT_SONG_PLAYLIST_HTML = `
|
||||||
The song currently playing is "%s", added <b>%s</b> from the playlist "%s".
|
The %s currently playing is "%s", added <b>%s</b> from the %s "%s".
|
||||||
`
|
`
|
||||||
|
|
|
@ -8,12 +8,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/jsonq"
|
||||||
"github.com/layeh/gumble/gumble"
|
"github.com/layeh/gumble/gumble"
|
||||||
"github.com/layeh/gumble/gumble_ffmpeg"
|
"github.com/layeh/gumble/gumble_ffmpeg"
|
||||||
)
|
)
|
||||||
|
@ -24,13 +30,14 @@ type YouTubeSong struct {
|
||||||
title string
|
title string
|
||||||
thumbnail string
|
thumbnail string
|
||||||
submitter *gumble.User
|
submitter *gumble.User
|
||||||
duration string
|
duration int
|
||||||
url string
|
url string
|
||||||
offset int
|
offset int
|
||||||
format string
|
format string
|
||||||
playlist Playlist
|
playlist Playlist
|
||||||
skippers []string
|
skippers []string
|
||||||
dontSkip bool
|
dontSkip bool
|
||||||
|
service Service
|
||||||
}
|
}
|
||||||
|
|
||||||
// YouTubePlaylist implements the Playlist interface
|
// YouTubePlaylist implements the Playlist interface
|
||||||
|
@ -80,9 +87,9 @@ func (dl *YouTubeSong) Play() {
|
||||||
panic(err)
|
panic(err)
|
||||||
} else {
|
} else {
|
||||||
message := `<table><tr><td align="center"><img src="%s" width=150 /></td></tr><tr><td align="center"><b><a href="%s">%s</a> (%s)</b></td></tr><tr><td align="center">Added by %s</td></tr>`
|
message := `<table><tr><td align="center"><img src="%s" width=150 /></td></tr><tr><td align="center"><b><a href="%s">%s</a> (%s)</b></td></tr><tr><td align="center">Added by %s</td></tr>`
|
||||||
message = fmt.Sprintf(message, dl.thumbnail, dl.url, dl.title, dl.duration, dl.submitter.Name)
|
message = fmt.Sprintf(message, dl.thumbnail, dl.url, dl.title, dl.Duration().String(), dl.submitter.Name)
|
||||||
if !isNil(dl.playlist) {
|
if !isNil(dl.playlist) {
|
||||||
message = fmt.Sprintf(message+`<tr><td align="center">From playlist "%s"</td></tr>`, dl.playlist.Title())
|
message = fmt.Sprintf(message+`<tr><td align="center">From playlist "%s"</td></tr>`, dl.Playlist().Title())
|
||||||
}
|
}
|
||||||
dj.client.Self.Channel.Send(message+`</table>`, false)
|
dj.client.Self.Channel.Send(message+`</table>`, false)
|
||||||
|
|
||||||
|
@ -162,9 +169,10 @@ func (dl *YouTubeSong) Filename() string {
|
||||||
return dl.id + "." + dl.format
|
return dl.id + "." + dl.format
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duration returns the duration of the Song.
|
// Duration returns duration for the Song.
|
||||||
func (dl *YouTubeSong) Duration() string {
|
func (dl *YouTubeSong) Duration() time.Duration {
|
||||||
return dl.duration
|
timeDuration, _ := time.ParseDuration(strconv.Itoa(dl.duration) + "s")
|
||||||
|
return timeDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thumbnail returns the thumbnail URL for the Song.
|
// Thumbnail returns the thumbnail URL for the Song.
|
||||||
|
@ -238,3 +246,31 @@ func (p *YouTubePlaylist) ID() string {
|
||||||
func (p *YouTubePlaylist) Title() string {
|
func (p *YouTubePlaylist) Title() string {
|
||||||
return p.title
|
return p.title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PerformGetRequest does all the grunt work for HTTPS GET request.
|
||||||
|
func PerformGetRequest(url string) (*jsonq.JsonQuery, error) {
|
||||||
|
jsonString := ""
|
||||||
|
|
||||||
|
if response, err := http.Get(url); err == nil {
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode == 200 {
|
||||||
|
if body, err := ioutil.ReadAll(response.Body); err == nil {
|
||||||
|
jsonString = string(body)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if response.StatusCode == 403 {
|
||||||
|
return nil, errors.New("Invalid API key supplied.")
|
||||||
|
}
|
||||||
|
return nil, errors.New("Invalid ID supplied.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("An error occurred while receiving HTTP GET response.")
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData := map[string]interface{}{}
|
||||||
|
decoder := json.NewDecoder(strings.NewReader(jsonString))
|
||||||
|
decoder.Decode(&jsonData)
|
||||||
|
jq := jsonq.NewQuery(jsonData)
|
||||||
|
|
||||||
|
return jq, nil
|
||||||
|
}
|
||||||
|
|
Reference in a new issue