2017-10-18 19:28:30 +02:00
#!/bin/sh
#
# Amazon Alexa Remote Control
# alex(at)loetzimmer.de
#
# 2017-10-10: v0.1 initial release
# 2017-10-11: v0.2 TuneIn Station Search
# 2017-10-11: v0.2a commands on special device "ALL" are executed on all ECHO+WHA
# 2017-10-16: v0.3 added playback of library tracks
2017-12-08 10:03:23 +01:00
# 2017-10-24: v0.4 added playback information
# 2017-11-21: v0.5 added Prime station and playlist
# 2017-11-22: v0.6 added Prime historical queue and replaced getopts
# 2017-11-25: v0.6a cURL is now configurable
# 2017-11-25: v0.7 added multiroom create/delete, playback of library playlist
# 2017-11-30: v0.7a added US config, fixed device names containing spaces
# 2017-12-07: v0.7b added Bluetooth connect/disconnect
2017-12-18 09:51:10 +01:00
# 2017-12-18: v0.7c fixed US version
2017-12-19 09:43:52 +01:00
# 2017-12-19: v0.7d fixed AWK csrf extraction on some systems
2017-12-20 18:55:59 +01:00
# 2017-12-20: v0.7e moved get_devlist after check_status
2018-01-10 08:24:36 +01:00
# 2018-01-08: v0.7f added echo-show to ALL group, TuneIn station can now be up to 6 digits
# 2018-01-08: v0.8 added bluetooth list function
# 2018-01-10: v0.8a abort when login was unsuccessful
2018-01-25 10:38:42 +01:00
# 2018-01-25: v0.8b added echo-spot to ALL group
2017-10-18 19:28:30 +02:00
#
###
#
# (no BASHisms were used, should run with any shell)
# - requires cURL for web communication
2018-01-10 08:24:36 +01:00
# - (GNU) sed and awk for extraction
2017-10-18 19:28:30 +02:00
# - jq as command line JSON parser (optional for the fancy bits)
#
##########################################
2017-12-08 10:03:23 +01:00
EMAIL = 'amazon_account@email.address'
PASSWORD = 'Very_Secret_Amazon_Account_Password'
LANGUAGE = "de,en"
#LANGUAGE="en-us"
AMAZON = 'amazon.de'
#AMAZON='amazon.com'
ALEXA = 'layla.amazon.de'
#ALEXA='pitangui.amazon.com'
# cURL binary
CURL = '/usr/bin/curl'
# cURL options
# -k : if your cURL cannot verify CA certificates, you'll have to trust any
# --compressed : if your cURL was compiled with libz you may use compression
OPTS = '--compressed'
#OPTS='-k --compressed'
2017-10-18 19:28:30 +02:00
###########################################
# nothing to configure below here
#
2017-12-08 10:03:23 +01:00
TMP = "/tmp"
COOKIE = " ${ TMP } /.alexa.cookie "
DEVLIST = " ${ TMP } /.alexa.devicelist.json "
2017-10-18 19:28:30 +02:00
2017-12-08 10:03:23 +01:00
GUIVERSION = 0
2017-10-18 19:28:30 +02:00
LIST = ""
LOGOFF = ""
COMMAND = ""
STATIONID = ""
2017-12-08 10:03:23 +01:00
QUEUE = ""
2017-10-18 19:28:30 +02:00
SONG = ""
2017-12-08 10:03:23 +01:00
TYPE = ""
ASIN = ""
SEEDID = ""
HIST = ""
LEMUR = ""
CHILD = ""
PLIST = ""
BLUETOOTH = ""
usage( )
{
2018-01-10 08:24:36 +01:00
echo " $0 [-d <device>|ALL] -e <pause|play|next|prev|fwd|rwd|shuffle|vol:<0-100>> | -b [list|<\"AA:BB:CC:DD:EE:FF\">] | -q | -r <\"station name\"|stationid> | -s <trackID> | -t <ASIN> | "
2017-12-08 10:03:23 +01:00
echo " -u <seedID> | -v <queueID> | -w <playlistId> | -i | -p | -P | -S | -a | -m <multiroom_device> [device_1 .. device_X] | -l | -h"
echo " -e : run command"
2018-01-10 08:24:36 +01:00
echo " -b : connect/disconnect/list bluetooth device"
2017-12-08 10:03:23 +01:00
echo " -q : query queue"
echo " -r : play tunein radio"
echo " -s : play library track"
echo " -t : play Prime playlist"
echo " -u : play Prime station"
echo " -v : play Prime historical queue"
echo " -w : play library playlist"
echo " -i : list imported library tracks"
echo " -p : list purchased library tracks"
echo " -P : list Prime playlists"
echo " -S : list Prime stations"
echo " -a : list available devices"
echo " -m : delete multiroom and/or create new multiroom containing devices"
echo " -l : logoff"
echo " -h : help"
}
while [ " $# " -gt 0 ] ; do
case " $1 " in
-d)
if [ " ${ 2 #- } " != " ${ 2 } " -o -z " $2 " ] ; then
echo " ERROR: missing argument for ${ 1 } "
usage
exit 1
fi
DEVICE = $2
shift
; ;
-e)
if [ " ${ 2 #- } " != " ${ 2 } " -o -z " $2 " ] ; then
echo " ERROR: missing argument for ${ 1 } "
usage
exit 1
fi
COMMAND = $2
shift
; ;
-b)
if [ " ${ 2 #- } " = " ${ 2 } " -a -n " $2 " ] ; then
BLUETOOTH = $2
shift
else
BLUETOOTH = "null"
fi
; ;
-m)
if [ " ${ 2 #- } " != " ${ 2 } " -o -z " $2 " ] ; then
echo " ERROR: missing argument for ${ 1 } "
usage
exit 1
fi
LEMUR = $2
shift
while [ " ${ 2 #- } " = " ${ 2 } " -a -n " $2 " ] ; do
CHILD = " ${ CHILD } ${ 2 } "
shift
done
; ;
-r)
if [ " ${ 2 #- } " != " ${ 2 } " -o -z " $2 " ] ; then
echo " ERROR: missing argument for ${ 1 } "
usage
exit 1
fi
STATIONID = $2
shift
# stationIDs are "s1234" or "s12345"
2018-01-10 08:24:36 +01:00
if [ -n " ${ STATIONID ##s[0-9][0-9][0-9][0-9] } " -a -n " ${ STATIONID ##s[0-9][0-9][0-9][0-9][0-9] } " -a -n " ${ STATIONID ##s[0-9][0-9][0-9][0-9][0-9][0-9] } " ] ; then
2017-12-08 10:03:23 +01:00
# search for station name
STATIONID = $( ${ CURL } ${ OPTS } -s --data-urlencode " query= ${ STATIONID } " -G "https://api.tunein.com/profiles?fullTextSearch=true" | jq -r '.Items[] | select(.ContainerType == "Stations") | .Children[] | select( .Index==1 ) | .GuideId' )
if [ -z " $STATIONID " ] ; then
echo " ERROR: no Station \" $2 \" found on TuneIn "
exit 1
fi
fi
; ;
-s)
if [ " ${ 2 #- } " != " ${ 2 } " -o -z " $2 " ] ; then
echo " ERROR: missing argument for ${ 1 } "
usage
exit 1
fi
SONG = $2
shift
; ;
-t)
if [ " ${ 2 #- } " != " ${ 2 } " -o -z " $2 " ] ; then
echo " ERROR: missing argument for ${ 1 } "
usage
exit 1
fi
ASIN = $2
shift
; ;
-u)
if [ " ${ 2 #- } " != " ${ 2 } " -o -z " $2 " ] ; then
echo " ERROR: missing argument for ${ 1 } "
usage
exit 1
fi
SEEDID = $2
shift
; ;
-v)
if [ " ${ 2 #- } " != " ${ 2 } " -o -z " $2 " ] ; then
echo " ERROR: missing argument for ${ 1 } "
usage
exit 1
fi
HIST = $2
shift
; ;
-w)
if [ " ${ 2 #- } " != " ${ 2 } " -o -z " $2 " ] ; then
echo " ERROR: missing argument for ${ 1 } "
usage
exit 1
fi
PLIST = $2
shift
; ;
-l)
LOGOFF = "true"
; ;
-a)
LIST = "true"
; ;
-i)
TYPE = "IMPORTED"
; ;
-p)
TYPE = "PURCHASES"
; ;
-P)
PRIME = "prime-playlist-browse-nodes"
; ;
-S)
PRIME = "prime-sections"
; ;
-q)
QUEUE = "true"
; ;
-h| -\? | --help)
usage
exit 0
; ;
*)
echo " ERROR: unknown option ${ 1 } "
usage
exit 1
; ;
esac
shift
2017-10-18 19:28:30 +02:00
done
case " $COMMAND " in
2017-12-08 10:03:23 +01:00
pause)
COMMAND = '{"type":"PauseCommand"}'
; ;
play)
COMMAND = '{"type":"PlayCommand"}'
; ;
next)
COMMAND = '{"type":"NextCommand"}'
; ;
prev)
COMMAND = '{"type":"PreviousCommand"}'
; ;
fwd)
COMMAND = '{"type":"ForwardCommand"}'
; ;
rwd)
COMMAND = '{"type":"RewindCommand"}'
; ;
shuffle)
COMMAND = '{"type":"ShuffleCommand","shuffle":"true"}'
; ;
vol:*)
VOL = ${ COMMAND ##* : }
# volume as integer!
if [ $VOL -le 100 -a $VOL -ge 0 ] ; then
COMMAND = '{"type":"VolumeLevelCommand","volumeLevel":' ${ VOL } '}'
else
echo "ERROR: volume should be an integer between 0 and 100"
usage
exit 1
fi
; ;
"" )
; ;
*)
echo " ERROR: unknown command \" ${ COMMAND } \"! "
usage
exit 1
; ;
2017-10-18 19:28:30 +02:00
esac
#
# Amazon Login
#
log_in( )
{
################################################################
#
# following headers are required:
2017-12-08 10:03:23 +01:00
# Accept-Language (possibly for determining login region)
# User-Agent (cURL wouldn't store cookies without)
2017-10-18 19:28:30 +02:00
#
################################################################
rm -f ${ DEVLIST }
rm -f ${ COOKIE }
2017-12-08 10:03:23 +01:00
rm -f ${ TMP } /.alexa.*.list
2017-10-18 19:28:30 +02:00
#
# get first cookie and write redirection target into referer
#
2017-12-08 10:03:23 +01:00
${ CURL } ${ OPTS } -s -D " ${ TMP } /.alexa.header " -c ${ COOKIE } -b ${ COOKIE } -A "Mozilla/5.0" -H " Accept-Language: ${ LANGUAGE } " -H "DNT: 1" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" -L\
https://alexa.${ AMAZON } | grep "hidden" | sed 's/hidden/\n/g' | grep "value=\"" | sed -r 's/^.*name="([^"]+)".*value="([^"]+)".*/\1=\2\&/g' > " ${ TMP } /.alexa.postdata "
2017-10-18 19:28:30 +02:00
#
# login empty to generate sessiion
#
2017-12-08 10:03:23 +01:00
${ CURL } ${ OPTS } -s -c ${ COOKIE } -b ${ COOKIE } -A "Mozilla/5.0" -H " Accept-Language: ${ LANGUAGE } " -H "DNT: 1" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" -L\
-H " $( grep 'Location: ' ${ TMP } /.alexa.header | sed 's/Location: /Referer: /' ) " -d " @ ${ TMP } /.alexa.postdata " https://www.${ AMAZON } /ap/signin | grep "hidden" | sed 's/hidden/\n/g' | grep "value=\"" | sed -r 's/^.*name="([^"]+)".*value="([^"]+)".*/\1=\2\&/g' > " ${ TMP } /.alexa.postdata2 "
2017-10-18 19:28:30 +02:00
#
# login with filled out form
# !!! referer now contains session in URL
#
2017-12-08 10:03:23 +01:00
${ CURL } ${ OPTS } -s -c ${ COOKIE } -b ${ COOKIE } -A "Mozilla/5.0" -H " Accept-Language: ${ LANGUAGE } " -H "DNT: 1" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" -L\
2017-12-19 09:43:52 +01:00
-H " Referer: https://www. ${ AMAZON } /ap/signin/ $( awk " \$0 ~/. ${ AMAZON } .*session-id[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " --data-urlencode " email= ${ EMAIL } " --data-urlencode " password= ${ PASSWORD } " -d " @ ${ TMP } /.alexa.postdata2 " https://www.${ AMAZON } /ap/signin > /dev/null
2017-10-18 19:28:30 +02:00
#
# get CSRF
#
2017-12-08 10:03:23 +01:00
${ CURL } ${ OPTS } -s -c ${ COOKIE } -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
https://${ ALEXA } /api/language > /dev/null
rm -f " ${ TMP } /.alexa.header "
rm -f " ${ TMP } /.alexa.postdata "
rm -f " ${ TMP } /.alexa.postdata2 "
}
2017-10-18 19:28:30 +02:00
#
# get JSON device list
#
2017-12-08 10:03:23 +01:00
get_devlist( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/devices-v2/device?cached=false " > ${ DEVLIST }
2017-10-18 19:28:30 +02:00
}
check_status( )
{
#
# bootstrap with GUI-Version writes GUI version to cookie
# returns among other the current authentication state
#
2017-12-08 10:03:23 +01:00
AUTHSTATUS = $( ${ CURL } ${ OPTS } -s -c ${ COOKIE } -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L https://${ ALEXA } /api/bootstrap?version= ${ GUIVERSION } | sed -r 's/^.*"authenticated":([^,]+),.*$/\1/g' )
2017-10-18 19:28:30 +02:00
2017-12-08 10:03:23 +01:00
if [ " $AUTHSTATUS " = "true" ] ; then
return 1
fi
2017-10-18 19:28:30 +02:00
2017-12-08 10:03:23 +01:00
return 0
2017-10-18 19:28:30 +02:00
}
#
# set device specific variables from JSON device list
#
set_var( )
{
2017-12-08 10:03:23 +01:00
DEVICE = $( echo ${ DEVICE } | sed -r 's/%20/ /g' )
if [ -z " ${ DEVICE } " ] ; then
# if no device was supplied, use the first Echo(dot) in device list
echo "setting default device to:"
2018-01-25 10:38:42 +01:00
DEVICE = $( jq -r '[ .devices[] | select(.deviceFamily == "ECHO" or .deviceFamily == "KNIGHT" or .deviceFamily == "ROOK" ) | .accountName] | .[0]' ${ DEVLIST } )
2017-12-08 10:03:23 +01:00
echo ${ DEVICE }
fi
DEVICETYPE = $( jq --arg device " ${ DEVICE } " -r '.devices[] | select(.accountName == $device) | .deviceType' ${ DEVLIST } )
DEVICESERIALNUMBER = $( jq --arg device " ${ DEVICE } " -r '.devices[] | select(.accountName == $device) | .serialNumber' ${ DEVLIST } )
MEDIAOWNERCUSTOMERID = $( jq --arg device " ${ DEVICE } " -r '.devices[] | select(.accountName == $device) | .deviceOwnerCustomerId' ${ DEVLIST } )
if [ -z " ${ DEVICESERIALNUMBER } " ] ; then
echo " ERROR: unkown device dev: ${ DEVICE } "
exit 1
fi
2017-10-18 19:28:30 +02:00
}
#
# list available devices from JSON device list
#
list_devices( )
{
2017-12-08 10:03:23 +01:00
jq -r '.devices[].accountName' ${ DEVLIST }
2017-10-18 19:28:30 +02:00
}
#
# execute command
#
run_cmd( )
{
2017-12-08 10:03:23 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST -d ${ COMMAND } \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/np/command?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } "
2017-10-18 19:28:30 +02:00
}
#
# play TuneIn radio station
#
play_radio( )
{
2017-12-08 10:03:23 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST\
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/tunein/queue-and-play?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } &guideId= ${ STATIONID } &contentType=station&callSign=&mediaOwnerCustomerId= ${ MEDIAOWNERCUSTOMERID } "
2017-10-18 19:28:30 +02:00
}
#
# play library track
#
play_song( )
{
2017-12-08 10:03:23 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST -d " {\"trackId\":\" ${ SONG } \",\"playQueuePrime\":true} " \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/cloudplayer/queue-and-play?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } &mediaOwnerCustomerId= ${ MEDIAOWNERCUSTOMERID } &shuffle=false "
}
#
# play library playlist
#
play_playlist( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST -d " {\"playlistId\":\" ${ PLIST } \",\"playQueuePrime\":true} " \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/cloudplayer/queue-and-play?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } &mediaOwnerCustomerId= ${ MEDIAOWNERCUSTOMERID } &shuffle=false "
}
#
# play PRIME playlist
#
play_prime_playlist( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST -d " {\"asin\":\" ${ ASIN } \"} " \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/prime/prime-playlist-queue-and-play?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } &mediaOwnerCustomerId= ${ MEDIAOWNERCUSTOMERID } "
}
#
# play PRIME station
#
play_prime_station( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST -d " {\"seed\":\"{\\\"type\\\":\\\"KEY\\\",\\\"seedId\\\":\\\" ${ SEEDID } \\\"}\",\"stationName\":\"none\",\"seedType\":\"KEY\"} " \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/gotham/queue-and-play?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } &mediaOwnerCustomerId= ${ MEDIAOWNERCUSTOMERID } "
}
#
# play PRIME historical queue
#
play_prime_hist_queue( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST -d " {\"deviceType\":\" ${ DEVICETYPE } \",\"deviceSerialNumber\":\" ${ DEVICESERIALNUMBER } \",\"mediaOwnerCustomerId\":\" ${ MEDIAOWNERCUSTOMERID } \",\"queueId\":\" ${ HIST } \",\"service\":null,\"trackSource\":\"TRACK\"} " \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/media/play-historical-queue "
}
#
# show library tracks
#
show_library( )
{
OFFSET = "" ;
SIZE = 50;
TOTAL = 0;
FILE = ${ TMP } /.alexa.${ TYPE } .list
if [ ! -f ${ FILE } ] ; then
echo -n '{"playlist":{"entryList":[' > ${ FILE }
while [ 50 -le ${ SIZE } ] ; do
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X GET \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/cloudplayer/playlists/ ${ TYPE } -V0-OBJECTID?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } &size= ${ SIZE } &offset= ${ OFFSET } &mediaOwnerCustomerId= ${ MEDIAOWNERCUSTOMERID } " > ${ FILE } .tmp
OFFSET = $( jq -r '.nextResultsToken' ${ FILE } .tmp)
SIZE = $( jq -r '.playlist | .trackCount' ${ FILE } .tmp)
jq -r -c '.playlist | .entryList' ${ FILE } .tmp >> ${ FILE }
echo "," >> ${ FILE }
TOTAL = $(( TOTAL+SIZE))
done
echo " []],\"trackCount\":\" ${ TOTAL } \"}} " >> ${ FILE }
rm -f ${ FILE } .tmp
fi
jq -r '.playlist.trackCount' ${ FILE }
jq '.playlist.entryList[] | .[]' ${ FILE }
2017-10-18 19:28:30 +02:00
}
2017-12-08 10:03:23 +01:00
#
# show Prime stations and playlists
#
show_prime( )
{
FILE = ${ TMP } /.alexa.${ PRIME } .list
if [ ! -f ${ FILE } ] ; then
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X GET \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/prime/{ $PRIME }?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } &mediaOwnerCustomerId= ${ MEDIAOWNERCUSTOMERID } " > ${ FILE }
if [ " $PRIME " = "prime-playlist-browse-nodes" ] ; then
for I in $( jq -r '.primePlaylistBrowseNodeList[].subNodes[].nodeId' ${ FILE } 2>/dev/null) ; do
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X GET \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/prime/prime-playlists-by-browse-node?browseNodeId= ${ I } &deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } &mediaOwnerCustomerId= ${ MEDIAOWNERCUSTOMERID } " >> ${ FILE }
done
fi
fi
jq '.' ${ FILE }
}
2017-10-18 19:28:30 +02:00
2017-12-08 10:03:23 +01:00
#
# current queue
#
show_queue( )
{
PARENT = ""
PARENTID = $( jq --arg device " ${ DEVICE } " -r '.devices[] | select(.accountName == $device) | .parentClusters[0]' ${ DEVLIST } )
if [ " $PARENTID " != "null" ] ; then
PARENTDEVICE = $( jq --arg serial ${ PARENTID } -r '.devices[] | select(.serialNumber == $serial) | .deviceType' ${ DEVLIST } )
PARENT = " &lemurId= ${ PARENTID } &lemurDeviceType= ${ PARENTDEVICE } "
fi
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X GET \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/np/player?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } ${ PARENT } " | jq '.'
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X GET \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/media/state?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } " | jq '.'
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X GET \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/np/queue?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } " | jq '.'
}
2017-10-18 19:28:30 +02:00
2017-12-08 10:03:23 +01:00
#
# deletes a multiroom device
#
delete_multiroom( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X DELETE \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/lemur/tail/ ${ DEVICESERIALNUMBER } "
}
#
# creates a multiroom device
#
create_multiroom( )
{
JSON = " {\"id\":null,\"name\":\" ${ LEMUR } \",\"members\":[ "
for DEVICE in $CHILD ; do
set_var
JSON = " ${ JSON } {\"dsn\":\" ${ DEVICESERIALNUMBER } \",\"deviceType\":\" ${ DEVICETYPE } \"}, "
done
JSON = " ${ JSON %, } ]} "
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST -d " ${ JSON } " \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/lemur/tail "
}
2018-01-10 08:24:36 +01:00
#
# list bluetooth devices
#
list_bluetooth( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X GET \
" https:// ${ ALEXA } /api/bluetooth?cached=false " | jq --arg serial " ${ DEVICESERIALNUMBER } " -r '.bluetoothStates[] | select(.deviceSerialNumber == $serial) | "\(.pairedDeviceList[]?.address) \(.pairedDeviceList[]?.friendlyName)"'
}
2017-12-08 10:03:23 +01:00
#
# connect bluetooth device
#
connect_bluetooth( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST -d " {\"bluetoothDeviceAddress\":\" ${ BLUETOOTH } \"} " \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/bluetooth/pair-sink/ ${ DEVICETYPE } / ${ DEVICESERIALNUMBER } "
}
#
# disconnect bluetooth device
#
disconnect_bluetooth( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2017-12-19 09:43:52 +01:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST \
2017-12-08 10:03:23 +01:00
" https:// ${ ALEXA } /api/bluetooth/disconnect-sink/ ${ DEVICETYPE } / ${ DEVICESERIALNUMBER } "
}
2017-10-18 19:28:30 +02:00
#
# logout
#
log_off( )
{
2017-12-08 10:03:23 +01:00
${ CURL } ${ OPTS } -s -c ${ COOKIE } -b ${ COOKIE } -A "Mozilla/5.0" -H "DNT: 1" -H "Connection: keep-alive" -L\
https://${ ALEXA } /logout > /dev/null
2017-10-18 19:28:30 +02:00
rm -f ${ DEVLIST }
rm -f ${ COOKIE }
2017-12-08 10:03:23 +01:00
rm -f ${ TMP } /.alexa.*.list
2017-10-18 19:28:30 +02:00
}
2017-12-08 10:03:23 +01:00
if [ -z " $BLUETOOTH " -a -z " $LEMUR " -a -z " $PLIST " -a -z " $HIST " -a -z " $SEEDID " -a -z " $ASIN " -a -z " $PRIME " -a -z " $TYPE " -a -z " $QUEUE " -a -z " $LIST " -a -z " $COMMAND " -a -z " $STATIONID " -a -z " $SONG " -a -n " $LOGOFF " ] ; then
echo "only logout option present, logging off ..."
log_off
exit 0
fi
if [ ! -f ${ COOKIE } ] ; then
echo "cookie do not exist. logging in ..."
log_in
2017-10-18 19:28:30 +02:00
fi
check_status
if [ $? -eq 0 ] ; then
2017-12-08 10:03:23 +01:00
echo "cookie expired, logging in again ..."
log_in
2018-01-09 23:58:52 +01:00
check_status
if [ $? -eq 0 ] ; then
echo "log in failed, aborting"
exit 1
fi
2017-10-18 19:28:30 +02:00
fi
2017-12-20 18:55:59 +01:00
if [ ! -f ${ DEVLIST } ] ; then
echo "device list do not exist. downloading ..."
get_devlist
2018-01-09 23:58:52 +01:00
if [ ! -f ${ DEVLIST } ] ; then
echo "failed to download device list, aborting"
exit 1
fi
2017-12-20 18:55:59 +01:00
fi
2017-12-08 10:03:23 +01:00
if [ -n " $COMMAND " -o -n " $QUEUE " ] ; then
if [ " ${ DEVICE } " = "ALL" ] ; then
2018-01-25 10:38:42 +01:00
for DEVICE in $( jq -r '.devices[] | select( .deviceFamily == "ECHO" or .deviceFamily == "KNIGHT" or .deviceFamily == "ROOK" or .deviceFamily == "WHA") | .accountName' ${ DEVLIST } | sed -r 's/ /%20/g' ) ; do
2017-12-08 10:03:23 +01:00
set_var
if [ -n " $COMMAND " ] ; then
echo " sending cmd: ${ COMMAND } to dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
run_cmd
else
echo " queue info for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
show_queue
fi
done
else
set_var
if [ -n " $COMMAND " ] ; then
echo " sending cmd: ${ COMMAND } to dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
run_cmd
else
echo " queue info for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
show_queue
fi
fi
elif [ -n " $LEMUR " ] ; then
DEVICESERIALNUMBER = $( jq --arg device " ${ LEMUR } " -r '.devices[] | select(.accountName == $device and .deviceFamily == "WHA") | .serialNumber' ${ DEVLIST } )
if [ -n " $DEVICESERIALNUMBER " ] ; then
delete_multiroom
else
if [ -z " $CHILD " ] ; then
echo " ERROR: ${ LEMUR } is no multiroom device. Cannot delete ${ LEMUR } " .
exit 1
fi
fi
if [ -z " $CHILD " ] ; then
echo " Deleted multi room dev: ${ LEMUR } serial: ${ DEVICESERIALNUMBER } "
else
echo " Creating multi room dev: ${ LEMUR } member_dev(s): ${ CHILD } "
create_multiroom
fi
rm -f ${ DEVLIST }
get_devlist
elif [ -n " $BLUETOOTH " ] ; then
2018-01-10 08:24:36 +01:00
if [ " $BLUETOOTH " = "list" -o " $BLUETOOTH " = "List" -o " $BLUETOOTH " = "LIST" ] ; then
if [ " ${ DEVICE } " = "ALL" ] ; then
2018-01-25 10:38:42 +01:00
for DEVICE in $( jq -r '.devices[] | select( .deviceFamily == "ECHO" or .deviceFamily == "KNIGHT" or .deviceFamily == "ROOK" or .deviceFamily == "WHA") | .accountName' ${ DEVLIST } | sed -r 's/ /%20/g' ) ; do
2018-01-10 08:24:36 +01:00
set_var
echo " bluetooth devices for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } : "
list_bluetooth
done
else
set_var
echo " bluetooth devices for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } : "
list_bluetooth
fi
elif [ " $BLUETOOTH " = "null" ] ; then
set_var
2017-12-08 10:03:23 +01:00
echo " disconnecting dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } from bluetooth "
disconnect_bluetooth
else
2018-01-10 08:24:36 +01:00
set_var
2017-12-08 10:03:23 +01:00
echo " connecting dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } to bluetooth device: ${ BLUETOOTH } "
connect_bluetooth
fi
2017-10-18 19:28:30 +02:00
elif [ -n " $STATIONID " ] ; then
2017-12-08 10:03:23 +01:00
set_var
echo " playing stationID: ${ STATIONID } on dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } mediaownerid: ${ MEDIAOWNERCUSTOMERID } "
play_radio
2017-10-18 19:28:30 +02:00
elif [ -n " $SONG " ] ; then
2017-12-08 10:03:23 +01:00
set_var
echo " playing library track: ${ SONG } on dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } mediaownerid: ${ MEDIAOWNERCUSTOMERID } "
play_song
elif [ -n " $PLIST " ] ; then
set_var
echo " playing library playlist: ${ PLIST } on dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } mediaownerid: ${ MEDIAOWNERCUSTOMERID } "
play_playlist
2017-10-18 19:28:30 +02:00
elif [ -n " $LIST " ] ; then
2017-12-08 10:03:23 +01:00
echo "the following devices exist in your account:"
list_devices
elif [ -n " $TYPE " ] ; then
set_var
echo -n " the following songs exist in your ${ TYPE } library: "
show_library
elif [ -n " $PRIME " ] ; then
set_var
echo " the following songs exist in your PRIME ${ PRIME } : "
show_prime
elif [ -n " $ASIN " ] ; then
set_var
echo " playing PRIME playlist ${ ASIN } "
play_prime_playlist
elif [ -n " $SEEDID " ] ; then
set_var
echo " playing PRIME station ${ SEEDID } "
play_prime_station
elif [ -n " $HIST " ] ; then
set_var
echo " playing PRIME historical queue ${ HIST } "
play_prime_hist_queue
2017-10-18 19:28:30 +02:00
else
2017-12-08 10:03:23 +01:00
echo "no alexa command received"
2017-10-18 19:28:30 +02:00
fi
if [ -n " $LOGOFF " ] ; then
2017-12-08 10:03:23 +01:00
echo "logout option present, logging off ..."
log_off
2017-10-18 19:28:30 +02:00
fi