Skip to content

Commit

Permalink
Add 'play --playlist' support. Issue #27 (#41)
Browse files Browse the repository at this point in the history
* Tests broken but playlist functionality added

* Fixed tests but need to make new tests for 'play-playlist'

* Update internal/play/play_test.go

Co-authored-by: Brian Strauch <[email protected]>

* Update internal/p/p_test.go

Co-authored-by: Brian Strauch <[email protected]>

* Syntax updates to mock_api.go

Co-authored-by: Brian Strauch <[email protected]>

* Added switch case to play functions and adapted to queryType - Requires PR on spotify

* Rename errors as per suggestion

Co-authored-by: Brian Strauch <[email protected]>

* Fixing Tests

Co-authored-by: Brian Strauch <[email protected]>

* Fixing Tests

Co-authored-by: Brian Strauch <[email protected]>

* Fixing Tests

Co-authored-by: Brian Strauch <[email protected]>

* Syntax cleanup

Co-authored-by: Brian Strauch <[email protected]>

* Syntax cleanup

Co-authored-by: Brian Strauch <[email protected]>

* Fixing Tests

Co-authored-by: Brian Strauch <[email protected]>

* Fixing command flags as per suggestion

Co-authored-by: Brian Strauch <[email protected]>

* Update internal/p/p_test.go

Co-authored-by: Brian Strauch <[email protected]>

* Syntax Updates

Co-authored-by: Brian Strauch <[email protected]>

* Update internal/errors.go

Co-authored-by: Brian Strauch <[email protected]>

* Updated p to better reflect play

* Revert "Merge branch 'playlist-#27' of https://github.com/Threpio/spotify-cli into playlist-#27"

This reverts commit 6fdf6dd, reversing
changes made to bdabdb5.

* updated to 0.7.0 -> Failed with some merging so git is messy

* Sorted out errors again

* More sorting out of play functions

* Running go fmt

* Forgot contextQuery Logic for p() function

Co-authored-by: Brian Strauch <[email protected]>
  • Loading branch information
Threpio and brianstrauch authored Aug 1, 2021
1 parent def54fb commit a48566e
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 43 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.16

require (
github.com/blang/semver v3.5.1+incompatible
github.com/brianstrauch/spotify v0.5.0
github.com/brianstrauch/spotify v0.7.0
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2
github.com/rhysd/go-github-selfupdate v1.2.3
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/brianstrauch/spotify v0.5.0 h1:IUEY5JPEPJJJdyVdpcH8ZzviGxI3Ra3huWPN+517MbU=
github.com/brianstrauch/spotify v0.5.0/go.mod h1:sImRT74obai+LNgFVxr+qNVq2yt5KEfvNxz6ZDfYWaA=
github.com/brianstrauch/spotify v0.6.0 h1:PGD6W9lJtKd8BUpgT+s/F1k8pFMoQ8fihM4vKbYkjCI=
github.com/brianstrauch/spotify v0.6.0/go.mod h1:KGmim5eCKSgu5y+jOrJPa8fxTFfy0GzeidodVnjGnUM=
github.com/brianstrauch/spotify v0.7.0 h1:6XvE6EocJHOgrWlL6FAPu26GhkIfLeo987Qca9q9gJs=
github.com/brianstrauch/spotify v0.7.0/go.mod h1:KGmim5eCKSgu5y+jOrJPa8fxTFfy0GzeidodVnjGnUM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
Expand Down
4 changes: 2 additions & 2 deletions internal/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ func WaitForUpdatedPlayback(api APIInterface, isUpdated func(playback *spotify.P
}
}

func Search(api APIInterface, query string) (*spotify.Track, error) {
page, err := api.Search(query, 1)
func Search(api APIInterface, query, searchType string) (*spotify.Track, error) {
page, err := api.Search(query, searchType, 1)
if err != nil {
return nil, err
}
Expand Down
5 changes: 3 additions & 2 deletions internal/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ const (
ErrAlreadyUpToDate = "Already up to date"
ErrLoginFailed = "Login failed"
ErrNoActiveDevice = "Player command failed: No active device found"
ErrNoDevices = "No devices"
ErrNoPlaylists = "No playlists"
ErrNoAlbums = "No albums found"
ErrNoDevices = "No devices found"
ErrNoPlaylists = "No playlists found"
ErrNoPrevious = "No track before this one"
ErrNotLoggedIn = `You are not logged in: Run "spotify login"`
ErrRestrictionViolated = "Player command failed: Restriction violated"
Expand Down
12 changes: 6 additions & 6 deletions internal/mock_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ type APIInterface interface {

GetPlayback() (*spotify.Playback, error)
GetDevices() ([]*spotify.Device, error)
Play(deviceID string, uris ...string) error
Play(deviceID, contextURI string, uris ...string) error
Pause(deviceID string) error
SkipToNextTrack() error
SkipToPreviousTrack() error
Repeat(state string) error
Shuffle(state bool) error
Queue(uri string) error

Search(q string, limit int) (*spotify.Paging, error)
Search(q, searchType string, limit int) (*spotify.Paging, error)
}

type MockAPI struct {
Expand Down Expand Up @@ -58,8 +58,8 @@ func (m *MockAPI) GetDevices() ([]*spotify.Device, error) {
return devices.([]*spotify.Device), err
}

func (m *MockAPI) Play(deviceID string, uris ...string) error {
args := m.Called(deviceID, uris)
func (m *MockAPI) Play(deviceID, contextURI string, uris ...string) error {
args := m.Called(deviceID, contextURI, uris)
return args.Error(0)
}

Expand Down Expand Up @@ -93,8 +93,8 @@ func (m *MockAPI) Queue(uri string) error {
return args.Error(0)
}

func (m *MockAPI) Search(q string, limit int) (*spotify.Paging, error) {
args := m.Called(q, limit)
func (m *MockAPI) Search(q, searchType string, limit int) (*spotify.Paging, error) {
args := m.Called(q, searchType, limit)

page := args.Get(0)
err := args.Error(1)
Expand Down
32 changes: 24 additions & 8 deletions internal/p/p.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package p

import (
"errors"
"github.com/spf13/cobra"
"spotify/internal"
"spotify/internal/pause"
"spotify/internal/play"
"strings"

"github.com/spf13/cobra"
)

func NewCommand() *cobra.Command {
Expand All @@ -20,15 +19,30 @@ func NewCommand() *cobra.Command {
if err != nil {
return err
}

query := strings.Join(args, " ")
queryType := "track"

deviceID, err := cmd.Flags().GetString("device-id")
if err != nil {
return err
}

status, err := p(api, query, deviceID)
contextQuery, err := cmd.Flags().GetString("playlist")
if err != nil {
return err
}

if contextQuery == "" {
contextQuery, err = cmd.Flags().GetString("album")
if err != nil {
return err
}
queryType = "album"
} else {
queryType = "playlist"
}

status, err := p(api, query, contextQuery, queryType, deviceID)
if err != nil {
return err
}
Expand All @@ -39,13 +53,15 @@ func NewCommand() *cobra.Command {
}

cmd.Flags().String("device-id", "", "device ID from 'spotify device list'")
cmd.Flags().String("playlist", "", "playlist name from 'spotify playlist list'")
cmd.Flags().String("album", "", "album name")

return cmd
}

func p(api internal.APIInterface, query, deviceID string) (string, error) {
if len(query) > 0 {
return play.Play(api, query, deviceID)
func p(api internal.APIInterface, query, contextQuery, queryType, deviceID string) (string, error) {
if len(query) > 0 || len(contextQuery) > 0 {
return play.Play(api, query, contextQuery, queryType, deviceID)
}

playback, err := api.GetPlayback()
Expand All @@ -60,6 +76,6 @@ func p(api internal.APIInterface, query, deviceID string) (string, error) {
if playback.IsPlaying {
return pause.Pause(api, deviceID)
} else {
return play.Play(api, "", deviceID)
return play.Play(api, query, contextQuery, queryType, deviceID)
}
}
14 changes: 7 additions & 7 deletions internal/p/p_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ func TestP_Play(t *testing.T) {

api.On("GetPlayback").Return(playback1, nil).Twice()
api.On("GetPlayback").Return(playback2, nil).Once()
api.On("Play", "", []string(nil)).Return(nil)
api.On("Play", "", "", "", []string(nil)).Return(nil)

status, err := p(api, "", "")
status, err := p(api, "", "", "", "")
require.NoError(t, err)
require.Equal(t, " Track\r🎵\n Artist\r🎤\n 0:00 [ ] 0:01\r▶️\n", status)
}
Expand Down Expand Up @@ -74,12 +74,12 @@ func TestP_Play_WithArgs(t *testing.T) {

query := "track"

api.On("Search", query, 1).Return(paging, nil)
api.On("Play", "", []string{uri}).Return(nil)
api.On("Search", query, "track", 1).Return(paging, nil)
api.On("Play", "", "", "", []string{uri}).Return(nil)
api.On("GetPlayback").Return(playback1, nil).Twice()
api.On("GetPlayback").Return(playback2, nil).Once()

status, err := p(api, query, "")
status, err := p(api, query, "", "", "")
require.NoError(t, err)
require.Equal(t, " Track\r🎵\n Artist\r🎤\n 0:00 [ ] 0:01\r▶️\n", status)
}
Expand Down Expand Up @@ -108,7 +108,7 @@ func TestP_Pause(t *testing.T) {
api.On("GetPlayback").Return(playback2, nil).Once()
api.On("Pause", "").Return(nil)

status, err := p(api, "", "")
status, err := p(api, "", "", "", "")
require.NoError(t, err)
require.Equal(t, " Track\r🎵\n Artist\r🎤\n 0:00 [ ] 0:01\r\n", status)
}
Expand All @@ -117,7 +117,7 @@ func TestP_ErrNoActiveDevice(t *testing.T) {
api := new(internal.MockAPI)
api.On("GetPlayback").Return(nil, nil)

_, err := p(api, "", "")
_, err := p(api, "", "", "", "")
require.Error(t, err)
require.Equal(t, internal.ErrNoActiveDevice, err.Error())
}
72 changes: 65 additions & 7 deletions internal/play/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,29 @@ func NewCommand() *cobra.Command {
}

query := strings.Join(args, " ")
queryType := "track"

deviceID, err := cmd.Flags().GetString("device-id")
if err != nil {
return err
}

status, err := Play(api, query, deviceID)
contextQuery, err := cmd.Flags().GetString("playlist")
if err != nil {
return err
}

if contextQuery == "" {
contextQuery, err = cmd.Flags().GetString("album")
if err != nil {
return err
}
queryType = "album"
} else {
queryType = "playlist"
}

status, err := Play(api, query, contextQuery, queryType, deviceID)
if err != nil {
return err
}
Expand All @@ -38,11 +54,13 @@ func NewCommand() *cobra.Command {
}

cmd.Flags().String("device-id", "", "device ID from 'spotify device list'")
cmd.Flags().String("playlist", "", "playlist name from 'spotify playlist list'")
cmd.Flags().String("album", "", "album name that you wish to play")

return cmd
}

func Play(api internal.APIInterface, query, deviceID string) (string, error) {
func Play(api internal.APIInterface, query, contextQuery, queryType, deviceID string) (string, error) {
playback, err := api.GetPlayback()
if err != nil {
return "", err
Expand All @@ -52,17 +70,56 @@ func Play(api internal.APIInterface, query, deviceID string) (string, error) {
return "", errors.New(internal.ErrNoActiveDevice)
}

if len(query) > 0 {
track, err := internal.Search(api, query)
switch queryType {
case "album":

api, err := internal.Authenticate()
if err != nil {
return "", err
}

paging, err := api.Search(contextQuery, "album", 1)
if err != nil {
return "", err
}

albums := paging.Albums.Items
if len(albums) == 0 {
return "", errors.New(internal.ErrNoAlbums)
}

if err := api.Play(deviceID, albums[0].URI); err != nil {
return "", err
}

case "playlist":
// Return a different API interface required for the playlist commands?
api, err := internal.Authenticate()
if err != nil {
return "", err
}

if err := api.Play(deviceID, track.URI); err != nil {
playlists, err := api.GetPlaylists()
if err != nil {
return "", err
}
} else {
if err := api.Play(deviceID); err != nil {

for _, playlist := range playlists {
if strings.EqualFold(playlist.Name, contextQuery) {
if err := api.Play(deviceID, playlist.URI); err != nil {
return "", err
}
break
}
}

default:
track, err := internal.Search(api, query, "track")
if err != nil {
return "", err
}

if err := api.Play(deviceID, "", track.URI); err != nil {
return "", err
}
}
Expand All @@ -76,4 +133,5 @@ func Play(api internal.APIInterface, query, deviceID string) (string, error) {
}

return status.Show(playback), nil

}
16 changes: 8 additions & 8 deletions internal/play/play_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ func TestPlay(t *testing.T) {

api.On("GetPlayback").Return(playback1, nil).Once()
api.On("GetPlayback").Return(playback2, nil).Once()
api.On("Play", "", []string(nil)).Return(nil)
api.On("Play", "", "", "", []string(nil)).Return(nil)

status, err := Play(api, "", "")
status, err := Play(api, "", "", "", "")
require.NoError(t, err)
require.Equal(t, " Track\r🎵\n Artist\r🎤\n 0:00 [ ] 0:01\r▶️\n", status)
}
Expand Down Expand Up @@ -75,22 +75,22 @@ func TestPlay_WithArgs(t *testing.T) {

query := "track"

api.On("Search", query, 1).Return(paging, nil)
api.On("Play", "", []string{uri}).Return(nil)
api.On("Search", query, "track", 1).Return(paging, nil)
api.On("Play", "", "", "", []string{uri}).Return(nil)
api.On("GetPlayback").Return(playback1, nil).Twice()
api.On("GetPlayback").Return(playback2, nil).Once()

status, err := Play(api, query, "")
status, err := Play(api, query, "", "", "")
require.NoError(t, err)
require.Equal(t, " Track\r🎵\n Artist\r🎤\n 0:00 [ ] 0:01\r▶️\n", status)
}

func TestPlay_ErrAlreadyPlaying(t *testing.T) {
api := new(internal.MockAPI)
api.On("GetPlayback").Return(new(spotify.Playback), nil)
api.On("Play", "", []string(nil)).Return(errors.New(internal.ErrRestrictionViolated))
api.On("Play", "", "", "", []string(nil)).Return(errors.New(internal.ErrRestrictionViolated))

_, err := Play(api, "", "")
_, err := Play(api, "", "", "", "")
require.Error(t, err)
require.Equal(t, internal.ErrRestrictionViolated, err.Error())
}
Expand All @@ -99,7 +99,7 @@ func TestPlay_ErrNoActiveDevice(t *testing.T) {
api := new(internal.MockAPI)
api.On("GetPlayback").Return(nil, nil)

_, err := Play(api, "", "")
_, err := Play(api, "", "", "", "")
require.Error(t, err)
require.Equal(t, internal.ErrNoActiveDevice, err.Error())
}
2 changes: 1 addition & 1 deletion internal/queue/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func NewCommand() *cobra.Command {
}

func Queue(api internal.APIInterface, query string) (string, error) {
track, err := internal.Search(api, query)
track, err := internal.Search(api, query, "track")
if err != nil {
return "", err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/queue/queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestQueue(t *testing.T) {

query := "track"

api.On("Search", query, 1).Return(paging, nil).Once()
api.On("Search", query, "track", 1).Return(paging, nil).Once()
api.On("Queue", uri).Return(nil)

output, err := Queue(api, query)
Expand Down

0 comments on commit a48566e

Please sign in to comment.