From c122a1f8e4e739f2debb2355fec643d844be0556 Mon Sep 17 00:00:00 2001 From: MichaelOultram Date: Sat, 26 Sep 2015 14:52:39 +0100 Subject: [PATCH] Song conditions are now checked in Service --- service.go | 74 ++++++++++++----- service_soundcloud.go | 90 +++++++++++--------- service_youtube.go | 188 ++++++++++++++++-------------------------- strings.go | 16 ++-- youtube_dl.go | 48 +++++++++-- 5 files changed, 222 insertions(+), 194 deletions(-) diff --git a/service.go b/service.go index 097c412..1e855b5 100644 --- a/service.go +++ b/service.go @@ -11,14 +11,17 @@ import ( "errors" "fmt" "regexp" + "time" "github.com/layeh/gumble/gumble" ) // Service interface. Each service will implement these functions type Service interface { + ServiceName() string + TrackName() string URLRegex(string) bool - NewRequest(*gumble.User, string) (string, error) + NewRequest(*gumble.User, string) ([]Song, error) } // Song interface. Each service will implement these @@ -34,7 +37,7 @@ type Song interface { Title() string ID() string Filename() string - Duration() string + Duration() time.Duration Thumbnail() string Playlist() Playlist DontSkip() bool @@ -54,7 +57,7 @@ type Playlist interface { var services []Service -// FindServiceAndAdd tries the given url with each service +// FindServiceAndAdd tries the given url with each service // and adds the song/playlist with the correct service func FindServiceAndAdd(user *gumble.User, url string) error { var urlService Service @@ -69,27 +72,58 @@ func FindServiceAndAdd(user *gumble.User, url string) error { if urlService == nil { return errors.New(INVALID_URL_MSG) } else { - oldLength := dj.queue.Len() var title string + var songsAdded = 0 + var songArray []Song var err error - if title, err = urlService.NewRequest(user, url); err == nil { - dj.client.Self.Channel.Send(fmt.Sprintf(SONG_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("FAILED_TO_DOWNLOAD") - } - } - } else { - dj.SendPrivateMessage(user, err.Error()) + // Get service to create songs + if songArray, err = urlService.NewRequest(user, url); err != nil { + return err } - 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 } } diff --git a/service_soundcloud.go b/service_soundcloud.go index 055a946..d49498b 100644 --- a/service_soundcloud.go +++ b/service_soundcloud.go @@ -13,7 +13,6 @@ import ( "os" "strconv" "strings" - "time" "github.com/jmoiron/jsonq" "github.com/layeh/gumble/gumble" @@ -30,42 +29,53 @@ type SoundCloud struct{} // 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 func (sc SoundCloud) URLRegex(url string) bool { return RegexpFromURL(url, []string{soundcloudSongPattern, soundcloudPlaylistPattern}) != nil } // 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 songArray []Song var err error 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")) 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") if err == nil { // PLAYLIST - if dj.HasPermission(user.Name, dj.conf.Permissions.AdminAddPlaylists) { - // Create playlist - title, _ := apiResponse.String("title") - permalink, _ := apiResponse.String("permalink_url") - playlist := &YouTubePlaylist{ - id: permalink, - title: title, - } - - // Add all tracks - for _, t := range tracks { - sc.NewSong(user, jsonq.NewQuery(t), 0, playlist) - } - return playlist.Title(), nil + // Create playlist + title, _ := apiResponse.String("title") + permalink, _ := apiResponse.String("permalink_url") + playlist := &YouTubePlaylist{ + id: permalink, + title: title, } - 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 { // SONG + // Calculate offset offset := 0 if len(timesplit) == 2 { timesplit = strings.Split(timesplit[1], ":") @@ -76,12 +86,17 @@ func (sc SoundCloud) NewRequest(user *gumble.User, url string) (string, error) { 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 -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") id, _ := trackData.Int("id") 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") } - // Check song is not longer than the MaxSongDuration - if dj.conf.General.MaxSongDuration == 0 || (durationMS/1000) <= dj.conf.General.MaxSongDuration { - timeDuration, _ := time.ParseDuration(strconv.Itoa(durationMS/1000) + "s") - duration := strings.NewReplacer("h", ":", "m", ":", "s", "").Replace(timeDuration.String()) - - song := &YouTubeSong{ - id: strconv.Itoa(id), - title: title, - url: url, - thumbnail: thumbnail, - submitter: user, - duration: duration, - offset: offset, - format: "mp3", - playlist: playlist, - skippers: make([]string, 0), - dontSkip: false, - } - dj.queue.AddSong(song) - return song.Title(), nil + song := &YouTubeSong{ + id: strconv.Itoa(id), + title: title, + url: url, + thumbnail: thumbnail, + submitter: user, + duration: durationMS / 1000, + offset: offset, + format: "mp3", + playlist: playlist, + skippers: make([]string, 0), + dontSkip: false, + service: sc, } - return "", errors.New(VIDEO_TOO_LONG_MSG) + return song, nil } diff --git a/service_youtube.go b/service_youtube.go index 8375dc4..f6e2e35 100644 --- a/service_youtube.go +++ b/service_youtube.go @@ -8,15 +8,13 @@ package main import ( - "encoding/json" "errors" "fmt" - "io/ioutil" - "net/http" "os" "regexp" "strconv" "strings" + "time" "github.com/jmoiron/jsonq" "github.com/layeh/gumble/gumble" @@ -35,23 +33,29 @@ var youtubeVideoPatterns = []string{ // YouTube implements the Service interface 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 func (yt YouTube) URLRegex(url string) bool { return RegexpFromURL(url, append(youtubeVideoPatterns, []string{youtubePlaylistPattern}...)) != nil } // 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 = "", "" if re, err := regexp.Compile(youtubePlaylistPattern); err == nil { if re.MatchString(url) { - if dj.HasPermission(user.Name, dj.conf.Permissions.AdminAddPlaylists) { - shortURL = re.FindStringSubmatch(url)[1] - playlist, err := yt.NewPlaylist(user, shortURL) - return playlist.Title(), err - } else { - return "", errors.New("NO_PLAYLIST_PERMISSION") - } + shortURL = re.FindStringSubmatch(url)[1] + return yt.NewPlaylist(user, shortURL) } else { re = RegexpFromURL(url, youtubeVideoPatterns) 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) if !isNil(song) { +<<<<<<< HEAD return song.Title(), err +======= + return append(songArray, song), nil +>>>>>>> abf98ad... Song conditions are now checked in Service } else { - return "", err + return nil, err } } } else { - return "", err + return nil, err } } // 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) { - var apiResponse *jsonq.JsonQuery - var err error - url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%s&key=%s", - id, os.Getenv("YOUTUBE_API_KEY")) - if apiResponse, err = PerformGetRequest(url); err != nil { - return nil, errors.New(INVALID_API_KEY) - } + url := fmt.Sprintf("https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%s&key=%s", id, os.Getenv("YOUTUBE_API_KEY")) + if apiResponse, err := PerformGetRequest(url); err == nil { + title, _ := apiResponse.String("items", "0", "snippet", "title") + thumbnail, _ := apiResponse.String("items", "0", "snippet", "thumbnails", "high", "url") + duration, _ := apiResponse.String("items", "0", "contentDetails", "duration") - var offsetDays, offsetHours, offsetMinutes, offsetSeconds int64 - if offset != "" { - offsetExp := regexp.MustCompile(`t\=(?P\d+d)?(?P\d+h)?(?P\d+m)?(?P\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\d+D)?T(?P\d+H)?(?P\d+M)?(?P\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{ submitter: user, title: title, id: id, url: "https://youtu.be/" + id, - offset: int((offsetDays * 86400) + (offsetHours * 3600) + (offsetMinutes * 60) + offsetSeconds), - duration: durationString, + offset: int(yt.parseTime(offset, `\?T\=(?P\d+D)?(?P\d+H)?(?P\d+M)?(?P\d+S)?`).Seconds()), + duration: int(yt.parseTime(duration, `P(?P\d+D)?T(?P\d+H)?(?P\d+M)?(?P\d+S)?`).Seconds()), thumbnail: thumbnail, format: "m4a", skippers: make([]string, 0), playlist: playlist, dontSkip: false, + service: yt, } - dj.queue.AddSong(song) <<<<<<< HEAD return song, nil @@ -281,12 +222,47 @@ func (s *YouTubeSong) SkipReached(channelUsers int) bool { return true >>>>>>> 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. -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 songArray []Song var err error // 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")) @@ -314,35 +290,9 @@ func (yt YouTube) NewPlaylist(user *gumble.User, id string) (Playlist, error) { for i := 0; i < numVideos; i++ { index := strconv.Itoa(i) videoID, _ := apiResponse.String("items", index, "snippet", "resourceId", "videoId") - yt.NewSong(user, videoID, "", playlist) - } - 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.") + if song, err := yt.NewSong(user, videoID, "", playlist); err == nil { + songArray = append(songArray, song) } - } 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 + return songArray, nil } diff --git a/strings.go b/strings.go index d48e7a7..76b7541 100644 --- a/strings.go +++ b/strings.go @@ -7,8 +7,8 @@ package main -// Message shown to users when the bot has an invalid YouTube API key. -const INVALID_API_KEY = "MumbleDJ does not have a valid 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 %s API key." // 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." @@ -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." // 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 // 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." // 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. -const INVALID_YOUTUBE_ID_MSG = "The YouTube URL you supplied did not contain a valid YouTube ID." +// Message shown to users when they supply an URL that does not contain a valid 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. const COMMENT_UPDATED_MSG = "The comment for the bot has successfully been updated." @@ -95,7 +95,7 @@ const PLAYLIST_SKIPPED_HTML = ` const HELP_HTML = `
User Commands:

!help - Displays this help.

-

!add - Adds songs to queue.

+

!add - Adds songs/playlists to queue.

!volume - Either tells you the current volume or sets it to a new volume.

!skip - Casts a vote to skip the current song

!skipplaylist - Casts a vote to skip over the current playlist.

@@ -168,5 +168,5 @@ const CURRENT_SONG_HTML = ` // Message shown to users when the currentsong command is issued when a song from a // playlist is playing. const CURRENT_SONG_PLAYLIST_HTML = ` - The song currently playing is "%s", added %s from the playlist "%s". + The %s currently playing is "%s", added %s from the %s "%s". ` diff --git a/youtube_dl.go b/youtube_dl.go index efd9963..9217529 100644 --- a/youtube_dl.go +++ b/youtube_dl.go @@ -8,12 +8,18 @@ package main import ( + "encoding/json" "errors" "fmt" + "io/ioutil" + "net/http" "os" "os/exec" + "strconv" + "strings" "time" + "github.com/jmoiron/jsonq" "github.com/layeh/gumble/gumble" "github.com/layeh/gumble/gumble_ffmpeg" ) @@ -24,13 +30,14 @@ type YouTubeSong struct { title string thumbnail string submitter *gumble.User - duration string + duration int url string offset int format string playlist Playlist skippers []string dontSkip bool + service Service } // YouTubePlaylist implements the Playlist interface @@ -80,9 +87,9 @@ func (dl *YouTubeSong) Play() { panic(err) } else { message := `` - 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) { - message = fmt.Sprintf(message+``, dl.playlist.Title()) + message = fmt.Sprintf(message+``, dl.Playlist().Title()) } dj.client.Self.Channel.Send(message+`
%s (%s)
Added by %s
From playlist "%s"
From playlist "%s"
`, false) @@ -162,9 +169,10 @@ func (dl *YouTubeSong) Filename() string { return dl.id + "." + dl.format } -// Duration returns the duration of the Song. -func (dl *YouTubeSong) Duration() string { - return dl.duration +// Duration returns duration for the Song. +func (dl *YouTubeSong) Duration() time.Duration { + timeDuration, _ := time.ParseDuration(strconv.Itoa(dl.duration) + "s") + return timeDuration } // Thumbnail returns the thumbnail URL for the Song. @@ -238,3 +246,31 @@ func (p *YouTubePlaylist) ID() string { func (p *YouTubePlaylist) Title() string { 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 +}