PR #139 from adn77/master

* Playing TuneIn works again using new entertainment API endpoint
* Added playmusic (Alexa.Music.PlaySearchPhrase) as command
This commit is contained in:
Alex 2021-09-02 15:24:48 +02:00 committed by GitHub
commit aa0b9261d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 18 deletions

View File

@ -22,9 +22,7 @@ DEVICEVOLNAME - a list of device names with specific volume settings (space se
DEVICEVOLSPEAK - a list of speak volume levels - matching the devices above DEVICEVOLSPEAK - a list of speak volume levels - matching the devices above
DEVICEVOLNORMAL - a list of normal volume levels- matching the devices above DEVICEVOLNORMAL - a list of normal volume levels- matching the devices above
(current playing volume takes precedence for normal volume) (current playing volume takes precedence for normal volume)
USE_ANNOUNCEMENT_FOR_SPEAK - Announcements can be made to multiple devices, while
regular SPEAK cannot but the announcement feature has
to be turned on for those devices. Also supports SSML!
``` ```
You will very likely want to set the language to: You will very likely want to set the language to:
``` ```
@ -41,9 +39,11 @@ alexa-remote-control [-d <device>|ALL] -e <pause|play|next|prev|fwd|rwd|shuffle|
-e : run command, additional SEQUENCECMDs: -e : run command, additional SEQUENCECMDs:
weather,traffic,flashbriefing,goodmorning,singasong,tellstory, weather,traffic,flashbriefing,goodmorning,singasong,tellstory,
speak:'<text/ssml>',automation:'<routine name>',sound:<soundeffect_name>, speak:'<text/ssml>',automation:'<routine name>',sound:<soundeffect_name>,
textcommand:'<anything you would otherwise say to Alexa>' textcommand:'<anything you would otherwise say to Alexa>',
playmusic:<channel e.g. TUNEIN, AMAZON_MUSIC>:'<music name>'
-b : connect/disconnect/list bluetooth device -b : connect/disconnect/list bluetooth device
-c : list 'playmusic' channels
-q : query queue -q : query queue
-n : query notifications -n : query notifications
-r : play tunein radio -r : play tunein radio

View File

@ -67,6 +67,9 @@
# (thanks to Ingo Fischer) # (thanks to Ingo Fischer)
# 2021-05-27: v0.18 complete rework of sequence commands especially for TTS # 2021-05-27: v0.18 complete rework of sequence commands especially for TTS
# Announcement feature is no longer required due to inconsistent SSML handling # Announcement feature is no longer required due to inconsistent SSML handling
# 2021-09-02: v0.19 Playing TuneIn works again using new entertainment API endpoint
# Added playmusic (Alexa.Music.PlaySearchPhrase) as command, for available channels use "-c"
# Note: playmusic is not multi-room capable, doing so might lead to unexpected results
# #
### ###
# #
@ -74,6 +77,7 @@
# - requires cURL for web communication # - requires cURL for web communication
# - (GNU) sed and awk for extraction # - (GNU) sed and awk for extraction
# - jq as command line JSON parser (optional for the fancy bits) # - jq as command line JSON parser (optional for the fancy bits)
# - base64 for B64 encoding (make sure "-w 0" option is available on your platform)
# - oathtool as OATH one-time password tool (optional for two-factor authentication) # - oathtool as OATH one-time password tool (optional for two-factor authentication)
# #
########################################## ##########################################
@ -167,7 +171,10 @@ TTS=""
UTTERANCE="" UTTERANCE=""
SEQUENCECMD="" SEQUENCECMD=""
SEQUENCEVAL="" SEQUENCEVAL=""
SEARCHPHRASE=""
PROVIDERID=""
STATIONID="" STATIONID=""
CHANNEL=""
QUEUE="" QUEUE=""
SONG="" SONG=""
ALBUM="" ALBUM=""
@ -188,15 +195,17 @@ NOTIFICATIONS=""
usage() usage()
{ {
echo "$0 [-d <device>|ALL] -e <pause|play|next|prev|fwd|rwd|shuffle|repeat|vol:<0-100>> |" echo "$0 [-d <device>|ALL] -e <pause|play|next|prev|fwd|rwd|shuffle|repeat|vol:<0-100>> |"
echo " -b [list|<\"AA:BB:CC:DD:EE:FF\">] | -q | -n | -r <\"station name\"|stationid> |" echo " -b [list|<\"AA:BB:CC:DD:EE:FF\">] | -q | -n | -r <\"station name\"|stationId> |"
echo " -s <trackID|'Artist' 'Album'> | -t <ASIN> | -u <seedID> | -v <queueID> | -w <playlistId> |" echo " -s <trackID|'Artist' 'Album'> | -t <ASIN> | -u <seedID> | -v <queueID> | -w <playlistId> |"
echo " -i | -p | -P | -S | -a | -m <multiroom_device> [device_1 .. device_X] | -lastalexa | -lastcommand | -z | -l | -h" echo " -i | -p | -P | -S | -a | -m <multiroom_device> [device_1 .. device_X] | -lastalexa | -lastcommand | -z | -l | -h"
echo echo
echo " -e : run command, additional SEQUENCECMDs:" echo " -e : run command, additional SEQUENCECMDs:"
echo " weather,traffic,flashbriefing,goodmorning,singasong,tellstory," echo " weather,traffic,flashbriefing,goodmorning,singasong,tellstory,"
echo " speak:'<text/ssml>',automation:'<routine name>',sound:<soundeffect_name>," echo " speak:'<text/ssml>',automation:'<routine name>',sound:<soundeffect_name>,"
echo " textcommand:'<anything you would otherwise say to Alexa>'" echo " textcommand:'<anything you would otherwise say to Alexa>',"
echo " playmusic:<channel e.g. TUNEIN, AMAZON_MUSIC>:'<music name>'"
echo " -b : connect/disconnect/list bluetooth device" echo " -b : connect/disconnect/list bluetooth device"
echo " -c : list 'playmusic' channels"
echo " -q : query queue" echo " -q : query queue"
echo " -n : query notifications" echo " -n : query notifications"
echo " -r : play tunein radio" echo " -r : play tunein radio"
@ -222,7 +231,7 @@ usage()
while [ "$#" -gt 0 ] ; do while [ "$#" -gt 0 ] ; do
case "$1" in case "$1" in
--version) --version)
echo "v0.18" echo "v0.19"
exit 0 exit 0
;; ;;
-d) -d)
@ -341,6 +350,9 @@ while [ "$#" -gt 0 ] ; do
-a) -a)
LIST="true" LIST="true"
;; ;;
-c)
CHANNEL="true"
;;
-i) -i)
TYPE="IMPORTED" TYPE="IMPORTED"
;; ;;
@ -455,6 +467,13 @@ case "$COMMAND" in
tellstory) tellstory)
SEQUENCECMD='Alexa.TellStory.Play' SEQUENCECMD='Alexa.TellStory.Play'
;; ;;
playmusic:*)
SEQUENCECMD='Alexa.Music.PlaySearchPhrase'
PROVIDERID=${COMMAND#*:}
PROVIDERID=${PROVIDERID%:*}
SEQUENCEVAL=',\"musicProviderId\":\"'${PROVIDERID}'\",'
SEARCHPHRASE=$(echo ${COMMAND##*:} | sed s/\"/\'/g)
;;
"") "")
;; ;;
*) *)
@ -626,6 +645,26 @@ list_devices()
jq -r '.devices[].accountName' ${DEVLIST} jq -r '.devices[].accountName' ${DEVLIST}
} }
#
# sanitize search phrase
# ARG1 - sequence command (e.g. Alexa.Music.PlaySearchPhrase)
# ARG2 - musicProviderID ( TUNEIN, AMASON_MUSIC, CLOUDPLAYER, SPOTIFY, APPLE_MUSIC, DEEZER, I_HEART_RADIO )
# ARG3 - search phrase
#
sanitize_search()
{
if [ -n "$1" -a -n "$2" -a -n "$3" ] ; then
JSON='{"type":"'${1}'","operationPayload":"{\"locale\":\"'${TTS_LOCALE}'\",\"musicProviderId\":\"'${2}'\",\"searchPhrase\":\"'${3}'\"}"}'
else
JSON='{"type":"'${SEQUENCECMD}'","operationPayload":"{\"locale\":\"'${TTS_LOCALE}'\",\"musicProviderId\":\"'${PROVIDERID}'\",\"searchPhrase\":\"'${SEARCHPHRASE}'\"}"}'
fi
${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -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 POST -d "${JSON}" \
"https://${ALEXA}/api/behaviors/operation/validate" | jq -r '.operationPayload.sanitizedSearchPhrase'
}
# #
# build node_to_execute string # build node_to_execute string
# ARG1 - SEQUENCECMD # ARG1 - SEQUENCECMD
@ -684,6 +723,14 @@ if [ -n "${SEQUENCECMD}" ] ; then
VOLUMEPRENODESTOEXECUTE='' VOLUMEPRENODESTOEXECUTE=''
VOLUMEPOSTNODESTOEXECUTE='' VOLUMEPOSTNODESTOEXECUTE=''
NODESTOEXECUTE='' NODESTOEXECUTE=''
# sanitize search phrase
if [ -n "${SEARCHPHRASE}" -a -n "${PROVIDERID}" ] ; then
SEQUENCEVAL=${SEQUENCEVAL}'\"searchPhrase\":\"'${SEARCHPHRASE}'\",\"sanitizedSearchPhrase\":\"'$(sanitize_search)'\"'
fi
# iterate over member devices if target is multiroom
# !!! this is no true multi-room - it just tries to play on every member device in parallel !!!
if [ "${DEVICEFAMILY}" = "WHA" ] ; then if [ "${DEVICEFAMILY}" = "WHA" ] ; then
MEMBERDEVICESERIALS=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .clusterMembers[]' ${DEVLIST}) MEMBERDEVICESERIALS=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .clusterMembers[]' ${DEVLIST})
for DEVICESERIALNUMBER in $MEMBERDEVICESERIALS ; do for DEVICESERIALNUMBER in $MEMBERDEVICESERIALS ; do
@ -691,7 +738,8 @@ if [ -n "${SEQUENCECMD}" ] ; then
NODESTOEXECUTE=$(add_node "$(node)" "${NODESTOEXECUTE}") NODESTOEXECUTE=$(add_node "$(node)" "${NODESTOEXECUTE}")
# add volume setting per device - the WHA volume is unrelyable # add volume setting per device - the WHA volume is unrelyable
if [ $SPEAKVOL -gt 0 -o -n "${DEVICEVOLSPEAK}" ] ; then # don't set volume if Alexa.Music.PlaySearchPhrase is used
if [ \( $SPEAKVOL -gt 0 -o -n "${DEVICEVOLSPEAK}" \) -a "${SEQUENCECMD}" != "Alexa.Music.PlaySearchPhrase" ] ; then
DEVICE=$(jq --arg device "${DEVICESERIALNUMBER}" -r '.devices[] | select(.serialNumber == $device) | .accountName' ${DEVLIST}) DEVICE=$(jq --arg device "${DEVICESERIALNUMBER}" -r '.devices[] | select(.serialNumber == $device) | .accountName' ${DEVLIST})
get_volumes get_volumes
VOLUMEPRENODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\"value\":\"'${SVOL}'\"') ${VOLUMEPRENODESTOEXECUTE}) VOLUMEPRENODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\"value\":\"'${SVOL}'\"') ${VOLUMEPRENODESTOEXECUTE})
@ -705,7 +753,9 @@ if [ -n "${SEQUENCECMD}" ] ; then
fi fi
else else
NODESTOEXECUTE=$(add_node "$(node)" "${NODESTOEXECUTE}") NODESTOEXECUTE=$(add_node "$(node)" "${NODESTOEXECUTE}")
if [ $SPEAKVOL -gt 0 -o -n "${DEVICEVOLSPEAK}" ] ; then
# don't set volume if Alexa.Music.PlaySearchPhrase is used
if [ \( $SPEAKVOL -gt 0 -o -n "${DEVICEVOLSPEAK}" \) -a "${SEQUENCECMD}" != "Alexa.Music.PlaySearchPhrase" ] ; then
get_volumes get_volumes
VOLUMEPRENODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\"value\":\"'${SVOL}'\"') ${VOLUMEPRENODESTOEXECUTE}) VOLUMEPRENODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\"value\":\"'${SVOL}'\"') ${VOLUMEPRENODESTOEXECUTE})
VOLUMEPOSTNODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\"value\":\"'${VOL}'\"') ${VOLUMEPOSTNODESTOEXECUTE}) VOLUMEPOSTNODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\"value\":\"'${VOL}'\"') ${VOLUMEPOSTNODESTOEXECUTE})
@ -730,7 +780,7 @@ if [ -n "${SEQUENCECMD}" ] ; then
-H "csrf: $(awk "\$0 ~/.${AMAZON}.*csrf[ \\s\\t]+/ {print \$7}" ${COOKIE})" -X POST -d @"${TMP}/.alexa.cmd" \ -H "csrf: $(awk "\$0 ~/.${AMAZON}.*csrf[ \\s\\t]+/ {print \$7}" ${COOKIE})" -X POST -d @"${TMP}/.alexa.cmd" \
"https://${ALEXA}/api/behaviors/preview" "https://${ALEXA}/api/behaviors/preview"
# rm -f "${TMP}/.alexa.cmd" rm -f "${TMP}/.alexa.cmd"
else else
${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep-alive" -L\ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -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 "Content-Type: application/json; charset=UTF-8" -H "Referer: https://alexa.${AMAZON}/spa/index.html" -H "Origin: https://alexa.${AMAZON}"\
@ -744,10 +794,12 @@ fi
# #
play_radio() play_radio()
{ {
JSON='{"contentToken":"music:'$(echo '["music/tuneIn/stationId","'${STATIONID}'"]|{"previousPageId":"TuneIn_SEARCH"}'| base64 -w 0| base64 -w 0 )'"}'
${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep-alive" -L\ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -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 "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 POST\ -H "csrf: $(awk "\$0 ~/.${AMAZON}.*csrf[ \\s\\t]+/ {print \$7}" ${COOKIE})" -X PUT -d "${JSON}" \
"https://${ALEXA}/api/tunein/queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&guideId=${STATIONID}&contentType=station&callSign=&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}" "https://${ALEXA}/api/entertainment/v1/player/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}"
} }
# #
@ -897,6 +949,14 @@ show_queue()
"https://${ALEXA}/api/np/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}" | jq '.' "https://${ALEXA}/api/np/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}" | jq '.'
} }
get_music_channels()
{
${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -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/behaviors/entities?skillId=amzn1.ask.1p.music" | jq -r '.[] | select( .supportedProperties[] == "Alexa.Music.PlaySearchPhrase" ) | "\(.id) - \(.displayName) \(.description)"'
}
# #
# device specific SPEAKVOL/NORMALVOL (sets SVOL/VOL) # device specific SPEAKVOL/NORMALVOL (sets SVOL/VOL)
# #
@ -1103,7 +1163,7 @@ rm -f ${TMP}/.alexa.*.list
rm -f ${TMP}/.alexa.volume.* rm -f ${TMP}/.alexa.volume.*
} }
if [ -z "$LASTALEXA" -a -z "$LASTCOMMAND" -a -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 "$NOTIFICATIONS" -a -z "$LIST" -a -z "$COMMAND" -a -z "$STATIONID" -a -z "$SONG" -a -z "$GETVOL" -a -n "$LOGOFF" ] ; then if [ -z "$LASTALEXA" -a -z "$LASTCOMMAND" -a -z "$CHANNEL" -a -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 "$NOTIFICATIONS" -a -z "$LIST" -a -z "$COMMAND" -a -z "$STATIONID" -a -z "$SONG" -a -z "$GETVOL" -a -n "$LOGOFF" ] ; then
echo "only logout option present, logging off ..." echo "only logout option present, logging off ..."
log_off log_off
exit 0 exit 0
@ -1139,6 +1199,11 @@ if [ -n "$LOGIN" ] ; then
exit 0 exit 0
fi fi
if [ -n "$CHANNEL" ] ; then
get_music_channels
exit 0
fi
if [ -n "$COMMAND" -o -n "$QUEUE" -o -n "$NOTIFICATIONS" -o -n "$GETVOL" ] ; then if [ -n "$COMMAND" -o -n "$QUEUE" -o -n "$NOTIFICATIONS" -o -n "$GETVOL" ] ; then
if [ "${DEVICE}" = "ALL" ] ; then if [ "${DEVICE}" = "ALL" ] ; then
for DEVICE in $( jq -r '.devices[] | select( ( .deviceFamily == "ECHO" or .deviceFamily == "KNIGHT" or .deviceFamily == "ROOK" or .deviceFamily == "WHA" ) and .online == true ) | .accountName' ${DEVLIST} | sed -r 's/ /%20/g') ; do for DEVICE in $( jq -r '.devices[] | select( ( .deviceFamily == "ECHO" or .deviceFamily == "KNIGHT" or .deviceFamily == "ROOK" or .deviceFamily == "WHA" ) and .online == true ) | .accountName' ${DEVLIST} | sed -r 's/ /%20/g') ; do

View File

@ -4,6 +4,7 @@
# alex(at)loetzimmer.de # alex(at)loetzimmer.de
# #
# 2021-01-28: v0.17c (for updates see http://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html) # 2021-01-28: v0.17c (for updates see http://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html)
# 2021-09-02: v0.17d includes fixes for playing tunein (base64 required)
# #
# !!! THIS IS THE FINAL VERSION !!! # !!! THIS IS THE FINAL VERSION !!!
# #
@ -15,6 +16,7 @@
# (no BASHisms were used, should run with any shell) # (no BASHisms were used, should run with any shell)
# - requires cURL for web communication # - requires cURL for web communication
# - (GNU) sed and awk for extraction # - (GNU) sed and awk for extraction
# - base64 for B64 encoding (make sure "-w 0" option is available on your platform)
# - oathtool as OATH one-time password tool (optional for two-factor authentication) # - oathtool as OATH one-time password tool (optional for two-factor authentication)
# #
########################################## ##########################################
@ -148,7 +150,7 @@ usage()
while [ "$#" -gt 0 ] ; do while [ "$#" -gt 0 ] ; do
case "$1" in case "$1" in
--version) --version)
echo "v0.17a" echo "v0.17d"
exit 0 exit 0
;; ;;
-d) -d)
@ -719,10 +721,12 @@ fi
# #
play_radio() play_radio()
{ {
JSON='{"contentToken":"music:'$(echo '["music/tuneIn/stationId","'${STATIONID}'"]|{"previousPageId":"TuneIn_SEARCH"}'| base64 -w 0| base64 -w 0 )'"}'
${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep-alive" -L\ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -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 "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 POST\ -H "csrf: $(awk "\$0 ~/.${AMAZON}.*csrf[ \\s\\t]+/ {print \$7}" ${COOKIE})" -X PUT -d "${JSON}" \
"https://${ALEXA}/api/tunein/queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&guideId=${STATIONID}&contentType=station&callSign=&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}" "https://${ALEXA}/api/entertainment/v1/player/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}"
} }
# #