Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ spotify share uri Displays the current song's Spotify URI and c

spotify toggle shuffle Toggles shuffle playback mode.
spotify toggle repeat Toggles repeat playback mode.

spotify list uri <format> <uri> List uri,title,artist and album of specific playlist, format in csv|tsv|text|html"
spotify list mine <format>* List 'my' playlists (uri, title, public), format in csv|tsv|texst|html";
* Please note that this requires authentication via a browser."
spotify -v | --verision Shows the shpotify and spotify versions.";
````

## Authors and contributing
Expand Down
221 changes: 196 additions & 25 deletions spotify
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

SHPOTIFY_VERSION="2.1.1_DM_fork"
USER_CONFIG_DEFAULTS="CLIENT_ID=\"\"\nCLIENT_SECRET=\"\"";
USER_CONFIG_FILE="${HOME}/.shpotify.cfg";
if ! [[ -f "${USER_CONFIG_FILE}" ]]; then
Expand All @@ -34,6 +35,9 @@ if ! [[ -f "${USER_CONFIG_FILE}" ]]; then
fi
source "${USER_CONFIG_FILE}";

SHPOTIFY_CREDENTIALS=$(printf "${CLIENT_ID}:${CLIENT_SECRET}" | base64 | tr -d "\n"|tr -d '\r');


# Set the percent change in volume for vol up and vol down
VOL_INCREMENT=10

Expand Down Expand Up @@ -94,9 +98,25 @@ showHelp () {
echo;
echo " toggle shuffle # Toggles shuffle playback mode.";
echo " toggle repeat # Toggles repeat playback mode.";
if [ $jq_installed = "true" ];then
echo;
echo " list uri <format> <uri> # List uri,title,artist and album of specific playlist-uri, format in csv|tsv|text|html";
echo " list mine <format>* # List 'my' playlists (uri, title, public), format in csv|tsv|texst|html";
echo " # * Please note that this requires authentication via a browser.";
echo " list devices <format> # List devices to play Spotify on. Devices must be active to be seen.";
echo;
echo " connect <device_id> # Connect and play music on another device. ID can be found with 'list devices'.";
fi
echo;
echo " -v | --version # Shows the shpotify and spotify versions.";
showAPIHelp
}

showVersion() {
cecho "shpotify.sh: $SHPOTIFY_VERSION";
cecho "spotify: `osascript -e 'get version of application "Spotify"'`"
}

cecho(){
bold=$(tput bold);
green=$(tput setaf 2);
Expand All @@ -116,6 +136,51 @@ showTrack() {
echo `osascript -e 'tell application "Spotify" to name of current track as string'`;
}

showTrackList() {
SPOTIFY_PLAYLIST_ID=${SPOTIFY_PLAYLIST_URI##*:}
TrackList=$( \
curl -s -G "https://api.spotify.com/v1/playlists/${SPOTIFY_PLAYLIST_ID}/tracks" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${SPOTIFY_ACCESS_TOKEN} " \
| jq -r --arg format "$format" '.items[] | [ .track.uri, .track.name, .track.artists[].name, .track.album.name] | @'$format'' \
)
cecho "$TrackList"
}

showMyPlaylists() {
MyPlaylists=$( \
curl -s -G "https://api.spotify.com/v1/me/playlists?limit=20" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${SPOTIFY_USER_ACCESS_TOKEN}" \
| jq -r --arg format "$format" '.items[] | [ .uri, .name, .public] | @'$format'' \
)
cecho "$MyPlaylists"
}

showMyDevices() {
MyDevices=$( \
curl -s -G "https://api.spotify.com/v1/me/player/devices" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${SPOTIFY_USER_ACCESS_TOKEN}" \
| jq -r --arg format "$format" '.devices[] | [ .id, .name, .type, .is_active, .volume_percent ] | @'$format'' \
)
cecho "$MyDevices"
}

selectDevice() {
curl -X "PUT" "https://api.spotify.com/v1/me/player" \
--data "{\"device_ids\":[\"${device_id}\"]}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${SPOTIFY_USER_ACCESS_TOKEN}"

sleep 2
osascript -e 'tell application "Spotify" to play';
}

showStatus () {
state=`osascript -e 'tell application "Spotify" to player state as string'`;
cecho "Spotify is currently $state.";
Expand Down Expand Up @@ -145,6 +210,86 @@ showStatus () {
echo -e $reset"Artist: $(showArtist)\nAlbum: $(showAlbum)\nTrack: $(showTrack) \nPosition: $position / $duration";
}

getUserAccessToken() {
## Based on a gist by Hugh Rawlinson (https://gist.github.com/hughrawlinson/358afa57a04c8c0f1ce4f1fd86604a73)
port=8082
redirect_uri=http%3A%2F%2Flocalhost%3A$port%2F
auth_endpoint=https://accounts.spotify.com/authorize/?response_type=code\&client_id=$CLIENT_ID\&redirect_uri=$redirect_uri
# TODO return cached access token if scopes match and it hasn't expired
# TODO get scopes from args
scopes="playlist-read-private user-read-playback-state user-modify-playback-state"
if [[ ! -z $scopes ]]
then
encoded_scopes=$(echo $scopes| tr ' ' '%' | sed s/%/%20/g)
# If scopes exists, then append them to auth_endpoint
auth_endpoint=$auth_endpoint\&scope=$encoded_scopes
fi

# If refresh_token exists and is valid for scopes, use refresh flow. No new login required
if [[ -r "/var/tmp/shpotify_refresh_token" ]];then
refresh_token=$(cat "/var/tmp/shpotify_refresh_token");
response=$(curl -s https://accounts.spotify.com/api/token \
-H "Content-Type:application/x-www-form-urlencoded" \
-H "Authorization: Basic $(echo -n $CLIENT_ID:$CLIENT_SECRET | base64)" \
-d "grant_type=refresh_token" \
-d "refresh_token=$refresh_token" \
)
else
open $auth_endpoint
# User is now authenticating on accounts.spotify.com...

# Now the user gets redirected to our endpoint
# Grab token and close browser window
### DM: closinig of window/tab doesn't work that well...
response=$(echo "HTTP/1.1 200 OK\nAccess-Control-Allow-Origin:*\nContent-Length:65\n\n<html><script>open(location, '_self').close();</script></html>\n" | nc -l -c $port)
code=$(echo "$response" | grep GET | cut -d' ' -f 2 | cut -d'=' -f 2)

response=$(curl -s https://accounts.spotify.com/api/token \
-H "Content-Type:application/x-www-form-urlencoded" \
-H "Authorization: Basic ${SHPOTIFY_CREDENTIALS}" \
-d "grant_type=authorization_code&code=$code&redirect_uri=http%3A%2F%2Flocalhost%3A$port%2F")

## refresh token only given during original authentication.
# Store the refrehs_token for later use
echo $response | jq -r '.refresh_token' > /var/tmp/shpotify_refresh_token
fi

# Output cache access token
SPOTIFY_USER_ACCESS_TOKEN=$(echo $response | jq -r '.access_token')
}

getAccessToken() {
cecho "Connecting to Spotify's API";

SPOTIFY_TOKEN_RESPONSE_DATA=$( \
curl "${SPOTIFY_TOKEN_URI}" \
--silent \
-X "POST" \
-H "Authorization: Basic ${SHPOTIFY_CREDENTIALS}" \
-d "grant_type=client_credentials" \
)
if ! [[ "${SPOTIFY_TOKEN_RESPONSE_DATA}" =~ "access_token" ]]; then
cecho "Autorization failed, please check ${USER_CONFG_FILE}"
cecho "${SPOTIFY_TOKEN_RESPONSE_DATA}"
showAPIHelp
exit 1
fi
SPOTIFY_ACCESS_TOKEN=$( \
printf "${SPOTIFY_TOKEN_RESPONSE_DATA}" \
| command grep -E -o '"access_token":".*",' \
| sed 's/"access_token"://g' \
| sed 's/"//g' \
| sed 's/,.*//g' \
)
}

## Check existence of jq
if [ -x "$(command -v jq)" ]; then
jq_installed="true"
else
jq_installed="false"
fi

if [ $# = 0 ]; then
showHelp;
else
Expand All @@ -158,6 +303,7 @@ else
sleep 2
fi
fi

while [ $# -gt 0 ]; do
arg=$1;

Expand All @@ -182,31 +328,6 @@ while [ $# -gt 0 ]; do
SHPOTIFY_CREDENTIALS=$(printf "${CLIENT_ID}:${CLIENT_SECRET}" | base64 | tr -d "\n"|tr -d '\r');
SPOTIFY_PLAY_URI="";

getAccessToken() {
cecho "Connecting to Spotify's API";

SPOTIFY_TOKEN_RESPONSE_DATA=$( \
curl "${SPOTIFY_TOKEN_URI}" \
--silent \
-X "POST" \
-H "Authorization: Basic ${SHPOTIFY_CREDENTIALS}" \
-d "grant_type=client_credentials" \
)
if ! [[ "${SPOTIFY_TOKEN_RESPONSE_DATA}" =~ "access_token" ]]; then
cecho "Autorization failed, please check ${USER_CONFG_FILE}"
cecho "${SPOTIFY_TOKEN_RESPONSE_DATA}"
showAPIHelp
exit 1
fi
SPOTIFY_ACCESS_TOKEN=$( \
printf "${SPOTIFY_TOKEN_RESPONSE_DATA}" \
| command grep -E -o '"access_token":".*",' \
| sed 's/"access_token"://g' \
| sed 's/"//g' \
| sed 's/,.*//g' \
)
}

searchAndPlay() {
type="$1"
Q="$2"
Expand Down Expand Up @@ -400,6 +521,52 @@ while [ $# -gt 0 ]; do
fi
break ;;

"list" )
if [[ $jq_installed == "false" ]];then
echo "Unfortunately the 'list' option will not function without 'jq', a json parser. It can be installed with \"brew install jq\""
else
if [ $# != 1 ]; then
# There are additional arguments, a status subcommand
if [[ "$3" == "csv" || "$3" == "tsv" || "$3" == "text" || "$3" == "html" ]]; then
format=$3
else
format="csv"
fi
case $2 in
"uri" )
array=( $@ );
len=${#array[@]};
SPOTIFY_TOKEN_URI="https://accounts.spotify.com/api/token";
getAccessToken;
SPOTIFY_PLAYLIST_URI=${array[@]:2:$len};
showTrackList;
break ;;
"mine" )
getUserAccessToken;
showMyPlaylists;
break ;;
"devices" )
getUserAccessToken;
showMyDevices;
break ;;
esac
fi
fi
break ;;

"connect" )
if [[ $jq_installed == "false" ]];then
echo "Unfortunately the 'connect' option will not function without 'jq', a json parser. It can be installed with \"brew install jq\""
else
if [ $# != 1 ]; then
# There are additional arguments, a status subcommand
device_id=$2
getUserAccessToken;
selectDevice;
fi
fi
break ;;

"info" )
info=`osascript -e 'tell application "Spotify"
set durSec to (duration of current track / 1000)
Expand Down Expand Up @@ -470,6 +637,10 @@ while [ $# -gt 0 ]; do
"help" )
showHelp;
break ;;

"-v" | "--version" )
showVersion | column -t;
break ;;
* )
showHelp;
exit 1;
Expand Down