Song conditions are now checked in Service

This commit is contained in:
MichaelOultram 2015-09-26 14:52:39 +01:00
parent 179594e445
commit 2880d78db1
5 changed files with 203 additions and 183 deletions

View file

@ -11,14 +11,18 @@ import (
"errors" "errors"
"fmt" "fmt"
"regexp" "regexp"
"strconv"
"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
@ -69,12 +73,43 @@ 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 err error var songsAdded = 0
var err errors
if title, err = urlService.NewRequest(user, url); err == nil { // Get service to create songs
if songArray, err = urlService.NewRequest(user, url); err != nil {
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 {
time, _ := time.ParseDuration(song.Duration())
if dj.conf.General.MaxSongDuration == 0 || int(time.Seconds()) <= dj.conf.General.MaxSongDuration {
if !isNil(song.Playlist()) {
title = song.Playlist().Title()
} else {
title = song.Title()
}
dj.queue.AddSong(song)
songsAdded++
}
}
if songsAdded == 0 {
return errors.New(TRACK_TOO_LONG_MSG)
} else if songsAdded == 1 {
dj.client.Self.Channel.Send(fmt.Sprintf(SONG_ADDED_HTML, user.Name, title), false) 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 // Starts playing the new song if nothing else is playing
if oldLength == 0 && dj.queue.Len() != 0 && !dj.audioStream.IsPlaying() { if oldLength == 0 && dj.queue.Len() != 0 && !dj.audioStream.IsPlaying() {
@ -83,13 +118,9 @@ func FindServiceAndAdd(user *gumble.User, url string) error {
} else { } else {
dj.queue.CurrentSong().Delete() dj.queue.CurrentSong().Delete()
dj.queue.OnSongFinished() dj.queue.OnSongFinished()
return errors.New("FAILED_TO_DOWNLOAD") return errors.New(AUDIO_FAIL_MSG)
} }
} }
} else {
dj.SendPrivateMessage(user, err.Error())
}
return err
} }
} }

View file

@ -30,14 +30,25 @@ 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() {
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"))
@ -48,7 +59,6 @@ func (sc SoundCloud) NewRequest(user *gumble.User, url string) (string, error) {
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")
@ -59,13 +69,14 @@ func (sc SoundCloud) NewRequest(user *gumble.User, url string) (string, error) {
// Add all tracks // Add all tracks
for _, t := range tracks { for _, t := range tracks {
sc.NewSong(user, jsonq.NewQuery(t), 0, playlist) if song, err = sc.NewSong(user, jsonq.NewQuery(t), 0, playlist); err == nil {
songArray = append(songArray, song)
} }
return playlist.Title(), nil
} }
return "", errors.New(NO_PLAYLIST_PERMISSION_MSG) 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 +87,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 nil, err
}
return append(songArray, song), 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,10 +109,8 @@ 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
if dj.conf.General.MaxSongDuration == 0 || (durationMS/1000) <= dj.conf.General.MaxSongDuration {
timeDuration, _ := time.ParseDuration(strconv.Itoa(durationMS/1000) + "s") timeDuration, _ := time.ParseDuration(strconv.Itoa(durationMS/1000) + "s")
duration := strings.NewReplacer("h", ":", "m", ":", "s", "").Replace(timeDuration.String()) duration := timeDuration.String() //Lazy way to display time
song := &YouTubeSong{ song := &YouTubeSong{
id: strconv.Itoa(id), id: strconv.Itoa(id),
@ -110,9 +124,7 @@ func (sc SoundCloud) NewSong(user *gumble.User, trackData *jsonq.JsonQuery, offs
playlist: playlist, playlist: playlist,
skippers: make([]string, 0), skippers: make([]string, 0),
dontSkip: false, dontSkip: false,
service: sc,
} }
dj.queue.AddSong(song) return song, nil
return song.Title(), nil
}
return "", errors.New(VIDEO_TOO_LONG_MSG)
} }

View file

@ -17,6 +17,7 @@ import (
"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 +36,30 @@ 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]
playlist, err := yt.NewPlaylist(user, shortURL) playlist, err := yt.NewPlaylist(user, shortURL)
return playlist.Title(), err 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)
@ -60,10 +68,11 @@ func (yt YouTube) NewRequest(user *gumble.User, url string) (string, error) {
startOffset = matches[0][2] startOffset = matches[0][2]
} }
song, err := yt.NewSong(user, shortURL, startOffset, nil) song, err := yt.NewSong(user, shortURL, startOffset, nil)
if !isNil(song) { if isNil(song) {
return song.Title(), nil songArray = append(songArray, song)
return songArray, nil
} else { } else {
return "", err return nil, err
} }
} }
} else { } else {
@ -73,44 +82,37 @@ func (yt YouTube) NewRequest(user *gumble.User, url string) (string, error) {
// 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",
id, os.Getenv("YOUTUBE_API_KEY"))
if apiResponse, err = PerformGetRequest(url); err != nil {
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") title, _ := apiResponse.String("items", "0", "snippet", "title")
thumbnail, _ := apiResponse.String("items", "0", "snippet", "thumbnails", "high", "url") thumbnail, _ := apiResponse.String("items", "0", "snippet", "thumbnails", "high", "url")
duration, _ := apiResponse.String("items", "0", "contentDetails", "duration") duration, _ := apiResponse.String("items", "0", "contentDetails", "duration")
var days, hours, minutes, seconds int64 song := &YouTubeSong{
submitter: user,
title: title,
id: id,
url: "https://youtu.be/" + id,
offset: yt.parseTime(offset).Seconds(),
duration: yt.parseTime(duration).Seconds(),
thumbnail: thumbnail,
format: "m4a",
skippers: make([]string, 0),
playlist: playlist,
dontSkip: false,
service: yt,
}
dj.queue.AddSong(song)
return song, nil
}
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 string) time.Duration {
var days, hours, minutes, seconds, totalSeconds int64
if duration != "" {
timestampExp := regexp.MustCompile(`P(?P<days>\d+D)?T(?P<hours>\d+H)?(?P<minutes>\d+M)?(?P<seconds>\d+S)?`) 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) timestampMatch := timestampExp.FindStringSubmatch(duration)
timestampResult := make(map[string]string) timestampResult := make(map[string]string)
@ -133,37 +135,11 @@ func (yt YouTube) NewSong(user *gumble.User, id, offset string, playlist Playlis
seconds, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["seconds"], "S"), 10, 32) seconds, _ = strconv.ParseInt(strings.TrimSuffix(timestampResult["seconds"], "S"), 10, 32)
} }
totalSeconds := int((days * 86400) + (hours * 3600) + (minutes * 60) + seconds) 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 { } else {
durationString = fmt.Sprintf("%d:%02d:%02d", hours, minutes, seconds) totalSeconds = 0
} }
} else { return time.ParseDuration(totalSeconds + "s")
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,
thumbnail: thumbnail,
format: "m4a",
skippers: make([]string, 0),
playlist: playlist,
dontSkip: false,
}
dj.queue.AddSong(song)
return song, nil
}
return nil, errors.New(VIDEO_TOO_LONG_MSG)
} }
// NewPlaylist gathers the metadata for a YouTube playlist and returns it. // NewPlaylist gathers the metadata for a YouTube playlist and returns it.
@ -200,31 +176,3 @@ func (yt YouTube) NewPlaylist(user *gumble.User, id string) (Playlist, error) {
} }
return playlist, nil 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.")
}
jsonData := map[string]interface{}{}
decoder := json.NewDecoder(strings.NewReader(jsonString))
decoder.Decode(&jsonData)
jq := jsonq.NewQuery(jsonData)
return jq, nil
}

View file

@ -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."
@ -78,7 +78,7 @@ const SONG_ADDED_HTML = `
// Message shown to channel when a playlist is added to the queue by a user. // Message shown to channel when a playlist is added to the queue by a user.
const PLAYLIST_ADDED_HTML = ` const PLAYLIST_ADDED_HTML = `
<b>%s</b> has added the playlist "%s" to the queue. <b>%s</b> has added the %s "%s" to the queue.
` `
// Message shown to channel when a song has been skipped. // Message shown to channel when a song has been skipped.
@ -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>
@ -132,12 +132,12 @@ const SUBMITTER_SKIP_HTML = `
// Message shown to users when another user votes to skip the current playlist. // Message shown to users when another user votes to skip the current playlist.
const PLAYLIST_SKIP_ADDED_HTML = ` const PLAYLIST_SKIP_ADDED_HTML = `
<b>%s</b> has voted to skip the current playlist. <b>%s</b> has voted to skip the current %s.
` `
// Message shown to users when the submitter of a song decides to skip their song. // Message shown to users when the submitter of a song decides to skip their song.
const PLAYLIST_SUBMITTER_SKIP_HTML = ` const PLAYLIST_SUBMITTER_SKIP_HTML = `
The current playlist has been skipped by <b>%s</b>, the submitter. The current %s has been skipped by <b>%s</b>, the submitter.
` `
// Message shown to users when they successfully change the volume. // Message shown to users when they successfully change the volume.
@ -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".
` `

View file

@ -31,6 +31,7 @@ type YouTubeSong struct {
playlist Playlist playlist Playlist
skippers []string skippers []string
dontSkip bool dontSkip bool
service Service
} }
// YouTubePlaylist implements the Playlist interface // YouTubePlaylist implements the Playlist interface
@ -238,3 +239,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
}