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
2018-01-28 18:33:16 +01:00
# 2018-01-28: v0.8c added configurable browser string
2018-03-01 22:35:22 +01:00
# 2018-02-17: v0.8d no need to write the cookie file on every "check_status"
# 2018-02-27: v0.8e added "lastalexa" option for HA-Bridge to send its command to a specific device
2019-07-08 21:12:09 +02:00
# (Markus Wennesheimer: https://wennez.wordpress.com/light-on-with-alexa-for-each-room/)
2018-03-01 22:35:22 +01:00
# 2018-02-27: v0.9 unsuccessful logins will now give a short info how to debug the login
2018-03-09 18:47:27 +01:00
# 2018-03-09: v0.9a workaround for login problem, force curl to use http1.1
2018-05-17 09:39:17 +02:00
# 2018-05-17: v0.9b update browser string and accept language
2018-06-18 17:56:30 +02:00
# 2018-05-23: v0.9c update accept language (again)
# 2018-06-12: v0.10 introducing TTS and more
2019-07-08 20:58:41 +02:00
# (thanks to Michael Geramb and his openHAB2 Amazon Echo Control binding)
2019-07-08 21:12:09 +02:00
# https://github.com/openhab/openhab2-addons/tree/master/addons/binding/org.openhab.binding.amazonechocontrol
# (thanks to Ralf Otto for implementing this feature in this script)
2018-06-18 17:56:30 +02:00
# 2018-06-13: v0.10a added album play of imported library
2019-01-23 00:07:58 +01:00
# 2018-06-18: v0.10b added Alexa routine execution
# 2019-01-22: v0.11 added repeat command, added environment variable parsing
2019-02-03 22:48:03 +01:00
# 2019-02-03: v0.11a fixed string escape for automation and speak commands
2019-02-10 20:04:53 +01:00
# 2019-02-10: v0.12 added "-d ALL" to the plain version, lastalexa now checks for SUCCESS activityStatus
2019-02-14 20:52:11 +01:00
# 2019-02-14: v0.12a reduced the number of replaced characters for TTS and automation
2019-06-28 23:14:00 +02:00
# 2019-06-18: v0.12b fixed CSRF
# 2019-06-28: v0.12c properly fixed CSRF
2019-07-08 20:58:41 +02:00
# 2019-07-08: v0.13 added support for Multi-Factor Authentication
# (thanks to rich-gepp https://github.com/rich-gepp)
2019-08-05 21:31:11 +02:00
# 2019-08-05: v0.14 added Volume setting via routine, and $SPEAKVOL
2019-11-18 13:39:18 +01:00
# 2019-11-18: v0.14a download 200 routines instead of only the first 20
2019-12-23 20:39:01 +01:00
# 2019-12-23: v0.14b Trigger routines by either utterance or routine name
2019-12-30 22:51:36 +01:00
# 2019-12-30: v0.15 re-worked the volume setting for TTS commands
2020-01-03 23:42:59 +01:00
# 2020-01-03: v0.15a introduce some proper "get_volume"
2020-01-08 21:34:53 +01:00
# 2020-01-08: v0.15b cleaned merge errors
2020-02-03 21:58:45 +01:00
# 2020-02-03: v0.15c SPEAKVOL of 0 leaves the volume setting untouched
2020-02-09 01:54:07 +01:00
# 2020-02-09: v0.16 TTS to Multiroom groups via USE_ANNOUNCEMENT_FOR_SPEAK + SSML for TTS
# (!!! requires Announcement feature to be enabled in each device !!!)
2020-02-09 20:53:03 +01:00
# 2020-02-09: v0.16a added sound library - only very few sounds are actually supported
# ( https://developer.amazon.com/en-US/docs/alexa/custom-skills/ask-soundlibrary.html )
2020-07-07 23:32:47 +02:00
# 2020-06-15: v0.16b added "lastcommand" option
# (thanks to Trinitus01 https://github.com/trinitus01)
# 2020-07-07: v0.16c fixed NORMALVOL if USE_ANNOUNCEMENT_FOR_SPEAK is set
2020-12-12 21:43:28 +01:00
# 2020-12-12: v0.17 added textcommand which lets you send anything via CLI you would otherwise say to Alexa
# ( https://github.com/thorsten-gehrig/alexa-remote-control/issues/108 )
2020-12-12 23:00:50 +01:00
# 2020-12-12: v0.17a sounds now benefit from SPEAKVOL
# fixed TuneIn IDs to also play podcasts
2021-01-28 21:31:27 +01:00
# 2021-01-28: v0.17b fixed new API endpoint for automations
# (thanks to Michael Winkler)
2021-01-28 23:35:48 +01:00
# 2021-01-28: v0.17c simplified volume detection using new DeviceVolumes endpoint
# (thanks to Ingo Fischer)
2021-05-27 22:51:01 +02:00
# 2021-05-27: v0.18 complete rework of sequence commands especially for TTS
# Announcement feature is no longer required due to inconsistent SSML handling
2021-09-02 15:19:46 +02:00
# 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
2021-09-13 23:14:24 +02:00
# 2021-09-13: v0.20 implemented device registration refresh_token cookie exchange flow as an alternative
2021-09-15 16:18:10 +02:00
# to logging in
# 2021-09-15: v0.20a optimized speak commands to use less JQ. This is useful in low-resource environments
2021-10-07 09:05:00 +02:00
# 2021-10-07: v0.20b fixed different cookie naming for amazon.com
2021-11-16 09:46:32 +01:00
# 2021-11-16: v0.20c fixed AlexaApp device selection: since they're all called "This Device" use corresponding
# line in /tmp/.alexa.devicelist.txt, e.g.: -d "This Device=A2TF17PFR55MTB=ce0123456789abcdef01=VOX"
# -lastalexa now returns this string. Make sure to put the device in double quotes!
2022-02-04 22:01:08 +01:00
# 2022-02-04: v0.20d minor volume fix (write volume to volume cache when volume is changed)
2022-06-30 05:21:19 +02:00
# 2022-06-29: v0.20e removed call to jq's strptime function, replaced with bash function using 'date' to convert to epoch
2024-01-29 23:31:21 +01:00
# 2024-01-29: v0.21 removed legacy login methods as they were no longer working
# implemented new API calls for -lastalexa and -lastcommand
# there is now an OS-type switch that hopefully handles OSX and BSD date creation
2024-01-31 14:01:17 +01:00
# 2024-01-31: v0.21a trying all different date options which come to mind (first working wins)
2024-04-04 22:42:16 +02:00
# 2024-02-01: v0.21b changed the output of -lastalexa back to the output of devicelist.txt
# 2024-04-06: v0.22 changed the date calculation once again, now the date processing ignores the actual cookie validity
# and simply sets it to "now + COOKIE_LIFETIME"
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)
2021-09-02 15:19:46 +02:00
# - base64 for B64 encoding (make sure "-w 0" option is available on your platform)
2017-10-18 19:28:30 +02:00
#
##########################################
2021-09-13 23:14:24 +02:00
# this can be obtained by doing the device registration login flow
2024-01-29 23:31:21 +01:00
# e.g. from here: https://github.com/adn77/alexa-cookie-cli/
2021-09-13 23:14:24 +02:00
SET_REFRESH_TOKEN = ''
2019-02-11 00:14:48 +01:00
SET_TTS_LOCALE = 'de-DE'
2019-01-23 00:07:58 +01:00
SET_AMAZON = 'amazon.de'
#SET_AMAZON='amazon.com'
2017-12-08 10:03:23 +01:00
2019-01-23 00:07:58 +01:00
SET_ALEXA = 'alexa.amazon.de'
#SET_ALEXA='pitangui.amazon.com'
2017-12-08 10:03:23 +01:00
# cURL binary
2019-01-23 00:07:58 +01:00
SET_CURL = '/usr/bin/curl'
2017-12-08 10:03:23 +01:00
# 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
2018-03-09 18:52:51 +01:00
# --http1.1 : cURL defaults to HTTP/2 on HTTPS connections if available
2019-01-23 00:07:58 +01:00
SET_OPTS = '--compressed --http1.1'
#SET_OPTS='-k --compressed --http1.1'
2017-10-18 19:28:30 +02:00
2018-01-28 18:33:16 +01:00
# browser identity
2019-01-23 00:07:58 +01:00
SET_BROWSER = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:1.0) bash-script/1.0'
2019-02-10 20:04:53 +01:00
#SET_BROWSER='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0'
2019-01-23 00:07:58 +01:00
2021-09-27 23:37:51 +02:00
# jq binary
SET_JQ = '/usr/bin/jq'
2019-01-23 00:07:58 +01:00
# tmp path
SET_TMP = "/tmp"
2018-01-28 18:33:16 +01:00
2020-02-03 21:58:45 +01:00
# Volume for speak commands (a SPEAKVOL of 0 leaves the volume settings untouched)
2020-02-09 01:54:07 +01:00
SET_SPEAKVOL = "0"
2019-08-05 21:31:11 +02:00
# if no current playing volume can be determined, fall back to normal volume
SET_NORMALVOL = "10"
2019-12-30 22:51:36 +01:00
# Device specific volumes (overriding the above)
2021-05-27 22:51:01 +02:00
# SET_DEVICEVOLNAME="EchoDot2ndGen Echo1stGen"
# SET_DEVICEVOLSPEAK="100 30"
# SET_DEVICEVOLNORMAL="100 20"
SET_DEVICEVOLNAME = ""
SET_DEVICEVOLSPEAK = ""
SET_DEVICEVOLNORMAL = ""
# max. age in minutes before volume is read from API (local cache time)
SET_VOLMAXAGE = "1"
2020-02-09 01:54:07 +01:00
2017-10-18 19:28:30 +02:00
###########################################
# nothing to configure below here
#
2019-01-23 00:07:58 +01:00
# retrieving environment variables if any are set
2021-09-13 23:14:24 +02:00
REFRESH_TOKEN = ${ REFRESH_TOKEN :- $SET_REFRESH_TOKEN }
2019-01-23 00:07:58 +01:00
AMAZON = ${ AMAZON :- $SET_AMAZON }
ALEXA = ${ ALEXA :- $SET_ALEXA }
BROWSER = ${ BROWSER :- $SET_BROWSER }
CURL = ${ CURL :- $SET_CURL }
OPTS = ${ OPTS :- $SET_OPTS }
2019-02-11 00:14:48 +01:00
TTS_LOCALE = ${ TTS_LOCALE :- $SET_TTS_LOCALE }
2019-01-23 00:07:58 +01:00
TMP = ${ TMP :- $SET_TMP }
2021-09-27 23:37:51 +02:00
JQ = ${ JQ :- $SET_JQ }
2019-08-05 21:31:11 +02:00
SPEAKVOL = ${ SPEAKVOL :- $SET_SPEAKVOL }
NORMALVOL = ${ NORMALVOL :- $SET_NORMALVOL }
2020-01-03 23:42:59 +01:00
VOLMAXAGE = ${ VOLMAXAGE :- $SET_VOLMAXAGE }
2019-12-30 22:51:36 +01:00
DEVICEVOLNAME = ${ DEVICEVOLNAME :- $SET_DEVICEVOLNAME }
DEVICEVOLSPEAK = ${ DEVICEVOLSPEAK :- $SET_DEVICEVOLSPEAK }
DEVICEVOLNORMAL = ${ DEVICEVOLNORMAL :- $SET_DEVICEVOLNORMAL }
2019-01-23 00:07:58 +01:00
2017-12-08 10:03:23 +01:00
COOKIE = " ${ TMP } /.alexa.cookie "
2021-09-15 16:18:10 +02:00
DEVLIST = " ${ TMP } /.alexa.devicelist "
2024-04-04 22:42:16 +02:00
COOKIE_LIFETIME = $(( 24 * 60 * 60 )) # default lifetime of one day before revalidation
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 = ""
2018-06-18 17:56:30 +02:00
TTS = ""
2018-06-18 20:27:31 +02:00
UTTERANCE = ""
2018-06-18 17:56:30 +02:00
SEQUENCECMD = ""
2019-08-05 21:31:11 +02:00
SEQUENCEVAL = ""
2021-09-02 15:19:46 +02:00
SEARCHPHRASE = ""
PROVIDERID = ""
2017-10-18 19:28:30 +02:00
STATIONID = ""
2021-09-02 15:19:46 +02:00
CHANNEL = ""
2017-12-08 10:03:23 +01:00
QUEUE = ""
2017-10-18 19:28:30 +02:00
SONG = ""
2018-06-18 17:56:30 +02:00
ALBUM = ""
ARTIST = ""
2017-12-08 10:03:23 +01:00
TYPE = ""
ASIN = ""
SEEDID = ""
HIST = ""
LEMUR = ""
CHILD = ""
PLIST = ""
BLUETOOTH = ""
2018-03-01 22:35:22 +01:00
LASTALEXA = ""
2020-06-20 11:06:09 +02:00
LASTCOMMAND = ""
2020-01-03 23:42:59 +01:00
GETVOL = ""
2020-01-08 21:34:53 +01:00
NOTIFICATIONS = ""
2017-12-08 10:03:23 +01:00
usage( )
{
2019-01-23 00:07:58 +01:00
echo " $0 [-d <device>|ALL] -e <pause|play|next|prev|fwd|rwd|shuffle|repeat|vol:<0-100>> | "
2021-09-02 15:19:46 +02:00
echo " -b [list|<\"AA:BB:CC:DD:EE:FF\">] | -q | -n | -r <\"station name\"|stationId> |"
2018-06-18 17:56:30 +02:00
echo " -s <trackID|'Artist' 'Album'> | -t <ASIN> | -u <seedID> | -v <queueID> | -w <playlistId> |"
2020-06-20 11:06:09 +02:00
echo " -i | -p | -P | -S | -a | -m <multiroom_device> [device_1 .. device_X] | -lastalexa | -lastcommand | -z | -l | -h"
2018-06-18 17:56:30 +02:00
echo
echo " -e : run command, additional SEQUENCECMDs:"
2020-02-09 20:53:03 +01:00
echo " weather,traffic,flashbriefing,goodmorning,singasong,tellstory,"
2020-12-12 21:43:28 +01:00
echo " speak:'<text/ssml>',automation:'<routine name>',sound:<soundeffect_name>,"
2021-09-02 15:19:46 +02:00
echo " textcommand:'<anything you would otherwise say to Alexa>',"
echo " playmusic:<channel e.g. TUNEIN, AMAZON_MUSIC>:'<music name>'"
2018-01-10 08:24:36 +01:00
echo " -b : connect/disconnect/list bluetooth device"
2021-09-02 15:19:46 +02:00
echo " -c : list 'playmusic' channels"
2017-12-08 10:03:23 +01:00
echo " -q : query queue"
2019-08-25 17:18:04 +02:00
echo " -n : query notifications"
2017-12-08 10:03:23 +01:00
echo " -r : play tunein radio"
2018-06-18 17:56:30 +02:00
echo " -s : play library track/library album"
2017-12-08 10:03:23 +01:00
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"
2018-03-01 22:35:22 +01:00
echo " -lastalexa : print device that received the last voice command"
2020-06-20 11:06:09 +02:00
echo " -lastcommand : print last voice command or last voice command of specific device"
2020-01-20 20:28:05 +01:00
echo " -z : print current volume level"
2020-01-22 09:41:16 +01:00
echo " -login : Logs in, without further command"
2017-12-08 10:03:23 +01:00
echo " -l : logoff"
echo " -h : help"
}
while [ " $# " -gt 0 ] ; do
case " $1 " in
2020-01-20 20:28:05 +01:00
--version)
2024-04-04 22:42:16 +02:00
echo "v0.22"
2020-01-20 20:28:05 +01:00
exit 0
; ;
2017-12-08 10:03:23 +01:00
-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
2020-12-12 23:00:50 +01:00
# stationIDs are "s1234" or "s12345"
if [ -n " ${ STATIONID ##s[0-9][0-9][0-9][0-9]* } " -a -n " ${ STATIONID ##p[0-9][0-9][0-9][0-9]* } " ] ; then
2017-12-08 10:03:23 +01:00
# search for station name
2021-09-27 23:37:51 +02:00
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' )
2017-12-08 10:03:23 +01:00
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
2018-06-18 17:56:30 +02:00
if [ " ${ 2 #- } " = " ${ 2 } " -a -n " $2 " ] ; then
ALBUM = $2
ARTIST = $SONG
shift
fi
2017-12-08 10:03:23 +01:00
; ;
-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
; ;
2020-01-22 09:41:16 +01:00
-login)
LOGIN = "true"
; ;
2017-12-08 10:03:23 +01:00
-l)
LOGOFF = "true"
; ;
-a)
LIST = "true"
; ;
2021-09-02 15:19:46 +02:00
-c)
CHANNEL = "true"
; ;
2017-12-08 10:03:23 +01:00
-i)
TYPE = "IMPORTED"
; ;
-p)
TYPE = "PURCHASES"
; ;
-P)
PRIME = "prime-playlist-browse-nodes"
; ;
-S)
PRIME = "prime-sections"
; ;
-q)
QUEUE = "true"
; ;
2019-08-25 17:18:04 +02:00
-n)
NOTIFICATIONS = "true"
; ;
2018-03-01 22:35:22 +01:00
-lastalexa)
LASTALEXA = "true"
; ;
2020-06-20 11:06:09 +02:00
-lastcommand)
LASTCOMMAND = "true"
; ;
2020-01-03 23:42:59 +01:00
-z)
GETVOL = "true"
; ;
2017-12-08 10:03:23 +01:00
-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"}'
; ;
2019-01-23 00:07:58 +01:00
repeat)
COMMAND = '{"type":"RepeatCommand","repeat":true}'
; ;
2017-12-08 10:03:23 +01:00
vol:*)
VOL = ${ COMMAND ##* : }
# volume as integer!
if [ $VOL -le 100 -a $VOL -ge 0 ] ; then
2019-08-05 21:31:11 +02:00
SEQUENCECMD = 'Alexa.DeviceControls.Volume'
SEQUENCEVAL = ',\"value\":\"' ${ VOL } '\"'
2017-12-08 10:03:23 +01:00
else
echo "ERROR: volume should be an integer between 0 and 100"
usage
exit 1
fi
; ;
2020-12-12 21:43:28 +01:00
textcommand:*)
SEQUENCECMD = 'Alexa.TextCommand\",\"skillId\":\"amzn1.ask.1p.tellalexa'
2021-05-27 22:51:01 +02:00
SEQUENCEVAL = $( echo ${ COMMAND ##textcommand : } | sed s/\" /\' /g)
2020-12-12 21:43:28 +01:00
SEQUENCEVAL = ',\"text\":\"' ${ SEQUENCEVAL } '\"'
; ;
2018-06-18 17:56:30 +02:00
speak:*)
2021-05-27 22:51:01 +02:00
TTS = $( echo ${ COMMAND ##speak : } | sed s/\" /\' /g)
TTS = ',\"textToSpeak\":\"' ${ TTS } '\"'
SEQUENCECMD = 'Alexa.Speak'
SEQUENCEVAL = $TTS
2018-06-18 17:56:30 +02:00
; ;
2020-02-09 20:53:03 +01:00
sound:*)
SEQUENCECMD = 'Alexa.Sound'
SEQUENCEVAL = ',\"soundStringId\":\"' ${ COMMAND ##sound : } '\"'
; ;
2018-06-18 20:27:31 +02:00
automation:*)
SEQUENCECMD = 'automation'
2020-02-09 01:54:07 +01:00
UTTERANCE = $( echo ${ COMMAND ##automation : } | sed -r 's/["\\]/ /g' )
2018-06-18 20:27:31 +02:00
; ;
2018-06-18 17:56:30 +02:00
weather)
SEQUENCECMD = 'Alexa.Weather.Play'
; ;
traffic)
SEQUENCECMD = 'Alexa.Traffic.Play'
; ;
flashbriefing)
SEQUENCECMD = 'Alexa.FlashBriefing.Play'
; ;
goodmorning)
SEQUENCECMD = 'Alexa.GoodMorning.Play'
; ;
singasong)
SEQUENCECMD = 'Alexa.SingASong.Play'
; ;
tellstory)
SEQUENCECMD = 'Alexa.TellStory.Play'
; ;
2021-09-02 15:19:46 +02:00
playmusic:*)
SEQUENCECMD = 'Alexa.Music.PlaySearchPhrase'
PROVIDERID = ${ COMMAND #* : }
PROVIDERID = ${ PROVIDERID % : * }
SEQUENCEVAL = ',\"musicProviderId\":\"' ${ PROVIDERID } '\",'
SEARCHPHRASE = $( echo ${ COMMAND ##* : } | sed s/\" /\' /g)
; ;
2017-12-08 10:03:23 +01:00
"" )
; ;
*)
echo " ERROR: unknown command \" ${ COMMAND } \"! "
usage
exit 1
; ;
2017-10-18 19:28:30 +02:00
esac
#
# Amazon Login
#
log_in( )
{
2021-09-15 16:18:10 +02:00
rm -f ${ DEVLIST } .json
2017-10-18 19:28:30 +02:00
rm -f ${ COOKIE }
2017-12-08 10:03:23 +01:00
rm -f ${ TMP } /.alexa.*.list
2017-10-18 19:28:30 +02:00
2021-09-13 23:14:24 +02:00
if [ -z " ${ REFRESH_TOKEN } " ] ; then
2024-01-29 23:31:21 +01:00
echo "Sorry, the very thing this project started with, namely the reverse engineered"
echo " login to the Amazon web page does no longer work. The Alexa login page has"
echo " been shut down in favor of a much more modern login process."
echo
echo "Please use the device login process https://github.com/adn77/alexa-cookie-cli"
echo " all you need is the 'refreshToken' looking sth. like 'Atnr|...'"
2024-02-01 22:01:10 +01:00
exit 1
2021-09-13 23:14:24 +02:00
else
2024-04-04 22:42:16 +02:00
now = $( date +%s)
exp = $(( now + COOKIE_LIFETIME ))
2021-09-14 10:20:25 +02:00
2024-04-04 22:42:16 +02:00
# the date processing ignores the actual cookie validity and simply sets it to "now + COOKIE_LIFETIME"
${ CURL } ${ OPTS } -s -X POST --data " app_name=Amazon%20Alexa&requested_token_type=auth_cookies&domain=www. ${ AMAZON } &source_token_type=refresh_token " --data-urlencode " source_token= ${ REFRESH_TOKEN } " -H " x-amzn-identity-auth-domain: api. ${ AMAZON } " https://api.${ AMAZON } /ap/exchangetoken/cookies | \
${ JQ } -r --arg exp $exp '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= $exp | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${ COOKIE }
2021-09-13 23:14:24 +02:00
2021-11-16 09:46:32 +01:00
if [ -z " $( grep " \. ${ AMAZON } .*\sat- " ${ COOKIE } ) " ] ; then
2021-09-13 23:14:24 +02:00
echo "ERROR: cookie retrieval with refresh_token didn't work"
exit 1
fi
2018-03-01 22:35:22 +01:00
fi
2017-10-18 19:28:30 +02:00
#
# get CSRF
#
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -c ${ COOKIE } -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2019-06-28 23:14:00 +02:00
https://${ ALEXA } /api/language > /dev/null
2021-11-16 09:46:32 +01:00
if [ -z " $( grep " \. ${ AMAZON } .*\scsrf " ${ COOKIE } ) " ] ; then
2019-06-28 23:14:00 +02:00
echo "trying to get CSRF from handlebars"
${ CURL } ${ OPTS } -s -c ${ COOKIE } -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
-H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
https://${ ALEXA } /templates/oobe/d-device-pick.handlebars > /dev/null
fi
2021-11-16 09:46:32 +01:00
if [ -z " $( grep " \. ${ AMAZON } .*\scsrf " ${ COOKIE } ) " ] ; then
2019-06-28 23:14:00 +02:00
echo "trying to get CSRF from devices-v2"
${ CURL } ${ OPTS } -s -c ${ COOKIE } -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
-H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
https://${ ALEXA } /api/devices-v2/device?cached= false > /dev/null
fi
2017-12-08 10:03:23 +01:00
2021-11-16 09:46:32 +01:00
if [ -z " $( grep " \. ${ AMAZON } .*\scsrf " ${ COOKIE } ) " ] ; then
2019-06-28 23:14:00 +02:00
echo "ERROR: no CSRF cookie received"
exit 1
fi
2017-12-08 10:03:23 +01:00
}
2017-10-18 19:28:30 +02:00
#
# get JSON device list
#
2017-12-08 10:03:23 +01:00
get_devlist( )
{
2021-09-15 16:18:10 +02:00
${ 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 } ) " \
" https:// ${ ALEXA } /api/devices-v2/device?cached=false " > ${ DEVLIST } .json
2021-09-27 23:37:51 +02:00
${ JQ } -r '.devices[] | "\(.accountName)=\(.deviceType)=\(.serialNumber)=\(.deviceFamily)"' ${ DEVLIST } .json > ${ DEVLIST } .txt
2021-11-16 09:46:32 +01:00
${ JQ } -r '.devices[] | select( .appDeviceList | length >0 ) as $p | .appDeviceList[] | "\($p.accountName)=\(.deviceType)=\(.serialNumber)=\($p.deviceFamily)"' ${ DEVLIST } .json >> ${ DEVLIST } .txt
2021-09-27 23:37:51 +02:00
${ JQ } -r '.devices[] | select(.deviceFamily == "WHA") | "\(.accountName)=\(.clusterMembers[])"' ${ DEVLIST } .json > ${ DEVLIST } _wha.txt
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
#
2019-01-23 01:00:37 +01:00
AUTHSTATUS = $( ${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L https://${ ALEXA } /api/bootstrap?version= ${ GUIVERSION } )
MEDIAOWNERCUSTOMERID = $( echo $AUTHSTATUS | sed -r 's/^.*"customerId":"([^,]+)",.*$/\1/g' )
AUTHSTATUS = $( echo $AUTHSTATUS | 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' )
2018-03-01 22:35:22 +01:00
2017-12-08 10:03:23 +01:00
if [ -z " ${ DEVICE } " ] ; then
# if no device was supplied, use the first Echo(dot) in device list
2021-09-15 16:18:10 +02:00
echo -n "setting default device to: "
DEVICE = $( grep -m 1 -E "ECHO|KNIGHT|ROOK" ${ DEVLIST } .txt | cut -d'=' -f1)
2017-12-08 10:03:23 +01:00
echo ${ DEVICE }
fi
2021-09-15 16:18:10 +02:00
DEVICESERIALNUMBER = $( grep -m 1 " ${ DEVICE } " ${ DEVLIST } .txt)
DEVICESERIALNUMBER = ${ DEVICESERIALNUMBER #*= }
2019-01-23 01:00:37 +01:00
2021-09-15 16:18:10 +02:00
DEVICEFAMILY = ${ DEVICESERIALNUMBER ##*= }
DEVICETYPE = ${ DEVICESERIALNUMBER %%=* }
DEVICESERIALNUMBER = ${ DEVICESERIALNUMBER #*= }
DEVICESERIALNUMBER = ${ DEVICESERIALNUMBER %=* }
2021-11-16 09:46:32 +01:00
2019-01-23 01:00:37 +01:00
# customerId is now retrieved from the logged in user
# the customerId in the device list is always from the user registering the device initially
2021-09-27 23:37:51 +02:00
# MEDIAOWNERCUSTOMERID=$(${JQ} --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .deviceOwnerCustomerId' ${DEVLIST}.json)
2017-12-08 10:03:23 +01:00
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( )
{
2021-09-27 23:37:51 +02:00
${ JQ } -r '.devices[].accountName' ${ DEVLIST } .json
2017-10-18 19:28:30 +02:00
}
2021-09-02 15:19:46 +02:00
#
# 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 } " \
2021-09-27 23:37:51 +02:00
" https:// ${ ALEXA } /api/behaviors/operation/validate " | ${ JQ } -r '.operationPayload.sanitizedSearchPhrase'
2021-09-02 15:19:46 +02:00
}
2021-05-27 22:51:01 +02:00
#
# build node_to_execute string
# ARG1 - SEQUENCECMD
# ARG2 - SEQUENCEVAL
#
node( )
{
if [ -n " $1 " -a -n " $2 " ] ; then
echo '{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"' ${ 1 } '\",\"operationPayload\":{\"deviceType\":\"' ${ DEVICETYPE } '\",\"deviceSerialNumber\":\"' ${ DEVICESERIALNUMBER } '\",\"customerId\":\"' ${ MEDIAOWNERCUSTOMERID } '\",\"locale\":\"' ${ TTS_LOCALE } '\"' ${ 2 } '}}'
else
echo '{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"' ${ SEQUENCECMD } '\",\"operationPayload\":{\"deviceType\":\"' ${ DEVICETYPE } '\",\"deviceSerialNumber\":\"' ${ DEVICESERIALNUMBER } '\",\"customerId\":\"' ${ MEDIAOWNERCUSTOMERID } '\",\"locale\":\"' ${ TTS_LOCALE } '\"' ${ SEQUENCEVAL } '}}'
fi
}
#
# create comma separated string
#
add_node( )
{
if [ -n " $1 " ] ; then
if [ -n " $2 " ] ; then
echo ${ 1 } ',' ${ 2 }
else
echo ${ 1 }
fi
fi
}
2017-10-18 19:28:30 +02:00
#
# execute command
#
run_cmd( )
{
2019-08-05 21:31:11 +02:00
if [ -n " ${ SEQUENCECMD } " ] ; then
if [ " ${ SEQUENCECMD } " = 'automation' ] ; then
${ 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 \
2021-01-28 21:31:27 +01:00
" https:// ${ ALEXA } /api/behaviors/v2/automations?limit=200 " > " ${ TMP } /.alexa.automation "
2019-08-05 21:31:11 +02:00
2021-09-27 23:37:51 +02:00
AUTOMATION = $( ${ JQ } --arg utterance " ${ UTTERANCE } " -r '.[] | select( .triggers[].payload.utterance == $utterance) | .automationId' " ${ TMP } /.alexa.automation " )
2019-08-05 21:31:11 +02:00
if [ -z " ${ AUTOMATION } " ] ; then
2021-09-27 23:37:51 +02:00
AUTOMATION = $( ${ JQ } --arg utterance " ${ UTTERANCE } " -r '.[] | select( .name == $utterance) | .automationId' " ${ TMP } /.alexa.automation " )
2019-12-23 20:39:01 +01:00
if [ -z " ${ AUTOMATION } " ] ; then
echo " ERROR: no such utterance ' ${ UTTERANCE } ' in Alexa routines "
rm -f " ${ TMP } /.alexa.automation "
exit 1
fi
2019-08-05 21:31:11 +02:00
fi
2021-09-27 23:37:51 +02:00
SEQUENCE = $( ${ JQ } --arg automation " ${ AUTOMATION } " -r -c '.[] | select( .automationId == $automation) | .sequence' " ${ TMP } /.alexa.automation " | sed 's/"/\\"/g' | sed " s/ALEXA_CURRENT_DEVICE_TYPE/ ${ DEVICETYPE } /g " | sed " s/ALEXA_CURRENT_DSN/ ${ DEVICESERIALNUMBER } /g " | sed " s/ALEXA_CUSTOMER_ID/ ${ MEDIAOWNERCUSTOMERID } /g " )
2019-08-05 21:31:11 +02:00
rm -f " ${ TMP } /.alexa.automation "
ALEXACMD = '{"behaviorId":"' ${ AUTOMATION } '","sequenceJson":"' ${ SEQUENCE } '","status":"ENABLED"}'
else
2021-05-27 22:51:01 +02:00
VOLUMEPRENODESTOEXECUTE = ''
VOLUMEPOSTNODESTOEXECUTE = ''
NODESTOEXECUTE = ''
2021-09-02 15:19:46 +02:00
# 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 !!!
2021-05-27 22:51:01 +02:00
if [ " ${ DEVICEFAMILY } " = "WHA" ] ; then
2021-09-15 16:18:10 +02:00
MEMBERDEVICESERIALS = $( grep " ${ DEVICE } " ${ DEVLIST } _wha.txt | cut -d'=' -f 2)
2021-05-27 22:51:01 +02:00
for DEVICESERIALNUMBER in $MEMBERDEVICESERIALS ; do
2021-09-15 16:18:10 +02:00
DEVICETYPE = $( grep " ${ DEVICESERIALNUMBER } " ${ DEVLIST } .txt | cut -d'=' -f 2)
2021-05-27 22:51:01 +02:00
NODESTOEXECUTE = $( add_node " $( node) " " ${ NODESTOEXECUTE } " )
2022-02-04 22:01:08 +01:00
# if SequenceCommand is "Alexa.DeviceControls.Volume" we have to adjust the local volume cache
if [ " $SEQUENCECMD " = "Alexa.DeviceControls.Volume" ] ; then
VOL = ${ SEQUENCEVAL % \\ \" }
VOL = ${ VOL ##* \\ \" }
if [ $VOL -gt 0 ] ; then
echo $VOL false > " ${ TMP } /.alexa.volume. ${ DEVICESERIALNUMBER } "
else
echo 0 true > " ${ TMP } /.alexa.volume. ${ DEVICESERIALNUMBER } "
fi
2021-05-27 22:51:01 +02:00
# add volume setting per device - the WHA volume is unrelyable
2021-09-02 15:19:46 +02:00
# don't set volume if Alexa.Music.PlaySearchPhrase is used
2022-02-04 22:01:08 +01:00
elif [ \( $SPEAKVOL -gt 0 -o -n " ${ DEVICEVOLSPEAK } " \) -a " ${ SEQUENCECMD } " != "Alexa.Music.PlaySearchPhrase" ] ; then
2021-09-15 16:18:10 +02:00
DEVICE = $( grep " ${ DEVICESERIALNUMBER } " ${ DEVLIST } .txt | cut -d'=' -f 1)
2021-05-27 22:51:01 +02:00
get_volumes
VOLUMEPRENODESTOEXECUTE = $( add_node $( node Alexa.DeviceControls.Volume ',\"value\":\"' ${ SVOL } '\"' ) ${ VOLUMEPRENODESTOEXECUTE } )
VOLUMEPOSTNODESTOEXECUTE = $( add_node $( node Alexa.DeviceControls.Volume ',\"value\":\"' ${ VOL } '\"' ) ${ VOLUMEPOSTNODESTOEXECUTE } )
2019-12-30 22:51:36 +01:00
fi
done
2021-05-27 22:51:01 +02:00
if [ -z " ${ NODESTOEXECUTE } " ] ; then
echo " No clusterMembers found for command: ${ COMMAND } on dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } family: ${ DEVICEFAMILY } "
return
2020-02-09 01:54:07 +01:00
fi
2018-06-18 20:27:31 +02:00
else
2021-05-27 22:51:01 +02:00
NODESTOEXECUTE = $( add_node " $( node) " " ${ NODESTOEXECUTE } " )
2021-09-02 15:19:46 +02:00
2022-02-04 22:01:08 +01:00
if [ " $SEQUENCECMD " = "Alexa.DeviceControls.Volume" ] ; then
VOL = ${ SEQUENCEVAL % \\ \" }
VOL = ${ VOL ##* \\ \" }
if [ $VOL -gt 0 ] ; then
echo $VOL false > " ${ TMP } /.alexa.volume. ${ DEVICESERIALNUMBER } "
else
echo 0 true > " ${ TMP } /.alexa.volume. ${ DEVICESERIALNUMBER } "
fi
2021-09-02 15:19:46 +02:00
# don't set volume if Alexa.Music.PlaySearchPhrase is used
2022-02-04 22:01:08 +01:00
elif [ \( $SPEAKVOL -gt 0 -o -n " ${ DEVICEVOLSPEAK } " \) -a " ${ SEQUENCECMD } " != "Alexa.Music.PlaySearchPhrase" ] ; then
2021-05-27 22:51:01 +02:00
get_volumes
VOLUMEPRENODESTOEXECUTE = $( add_node $( node Alexa.DeviceControls.Volume ',\"value\":\"' ${ SVOL } '\"' ) ${ VOLUMEPRENODESTOEXECUTE } )
VOLUMEPOSTNODESTOEXECUTE = $( add_node $( node Alexa.DeviceControls.Volume ',\"value\":\"' ${ VOL } '\"' ) ${ VOLUMEPOSTNODESTOEXECUTE } )
2020-02-09 01:54:07 +01:00
fi
2018-06-18 20:27:31 +02:00
fi
2021-05-27 22:51:01 +02:00
if [ -n " ${ VOLUMEPRENODESTOEXECUTE } " -a -n " ${ VOLUMEPOSTNODESTOEXECUTE } " ] ; then
# execute serially "set_speak_volume" => "sequence_command" => "set_normal_volume"
# (each subtask is executed in parallel)
ALEXACMD = '{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.SerialNode\",\"nodesToExecute\":[{\"@type\":\"com.amazon.alexa.behaviors.model.ParallelNode\",\"nodesToExecute\":[' ${ VOLUMEPRENODESTOEXECUTE } ']},{\"@type\":\"com.amazon.alexa.behaviors.model.ParallelNode\",\"nodesToExecute\":[' ${ NODESTOEXECUTE } ']},{\"@type\":\"com.amazon.alexa.behaviors.model.ParallelNode\",\"nodesToExecute\":[' ${ VOLUMEPOSTNODESTOEXECUTE } ']}]}}","status":"ENABLED"}'
else
# execute in parallel "sequence_command"
ALEXACMD = '{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.ParallelNode\",\"nodesToExecute\":[' ${ NODESTOEXECUTE } ']}}","status":"ENABLED"}'
fi
2019-08-05 21:31:11 +02:00
fi
2018-06-18 17:56:30 +02:00
2019-08-05 21:31:11 +02:00
# Due to some weird shell-escape-behavior the command has to be written to a file before POSTing it
echo $ALEXACMD > " ${ TMP } /.alexa.cmd "
2019-02-03 22:48:03 +01:00
2019-08-05 21:31:11 +02:00
${ 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 @" ${ TMP } /.alexa.cmd " \
" https:// ${ ALEXA } /api/behaviors/preview "
2019-02-03 22:48:03 +01:00
2021-09-02 15:19:46 +02:00
rm -f " ${ TMP } /.alexa.cmd "
2018-06-18 17:56:30 +02:00
else
${ 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 ${ COMMAND } \
" https:// ${ ALEXA } /api/np/command?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } "
fi
2017-10-18 19:28:30 +02:00
}
#
# play TuneIn radio station
#
play_radio( )
{
2021-09-02 15:19:46 +02:00
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\
2017-12-08 10:03:23 +01:00
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2021-09-02 15:19:46 +02:00
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X PUT -d " ${ JSON } " \
" https:// ${ ALEXA } /api/entertainment/v1/player/queue?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } "
2017-10-18 19:28:30 +02:00
}
#
# play library track
#
play_song( )
{
2018-06-18 17:56:30 +02:00
if [ -z " ${ ALBUM } " ] ; then
JSON = " {\"trackId\":\" ${ SONG } \",\"playQueuePrime\":true} "
else
JSON = " {\"albumArtistName\":\" ${ ARTIST } \",\"albumName\":\" ${ ALBUM } \"} "
fi
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-H "Content-Type: application/json; charset=UTF-8" -H " Referer: https://alexa. ${ AMAZON } /spa/index.html " -H " Origin: https://alexa. ${ AMAZON } " \
2018-06-18 17:56:30 +02: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/cloudplayer/queue-and-play?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } &mediaOwnerCustomerId= ${ MEDIAOWNERCUSTOMERID } &shuffle=false "
}
#
# play library playlist
#
play_playlist( )
{
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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( )
{
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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( )
{
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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( )
{
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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
2021-09-27 23:37:51 +02:00
OFFSET = $( ${ JQ } -r '.nextResultsToken' ${ FILE } .tmp)
SIZE = $( ${ JQ } -r '.playlist | .trackCount' ${ FILE } .tmp)
${ JQ } -r -c '.playlist | .entryList' ${ FILE } .tmp >> ${ FILE }
2017-12-08 10:03:23 +01:00
echo "," >> ${ FILE }
TOTAL = $(( TOTAL+SIZE))
done
echo " []],\"trackCount\":\" ${ TOTAL } \"}} " >> ${ FILE }
rm -f ${ FILE } .tmp
fi
2021-09-27 23:37:51 +02:00
${ 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
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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
2021-09-27 23:37:51 +02:00
for I in $( ${ JQ } -r '.primePlaylistBrowseNodeList[].subNodes[].nodeId' ${ FILE } 2>/dev/null) ; do
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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
2021-09-27 23:37:51 +02:00
${ JQ } '.' ${ FILE }
2017-12-08 10:03:23 +01:00
}
2017-10-18 19:28:30 +02:00
2017-12-08 10:03:23 +01:00
#
# current queue
#
show_queue( )
{
PARENT = ""
2021-09-27 23:37:51 +02:00
PARENTID = $( ${ JQ } --arg device " ${ DEVICE } " -r '.devices[] | select(.accountName == $device) | .parentClusters[0]' ${ DEVLIST } .json)
2017-12-08 10:03:23 +01:00
if [ " $PARENTID " != "null" ] ; then
2021-09-27 23:37:51 +02:00
PARENTDEVICE = $( ${ JQ } --arg serial ${ PARENTID } -r '.devices[] | select(.serialNumber == $serial) | .deviceType' ${ DEVLIST } .json)
2017-12-08 10:03:23 +01:00
PARENT = " &lemurId= ${ PARENTID } &lemurDeviceType= ${ PARENTDEVICE } "
fi
2021-09-15 16:18:10 +02:00
2018-06-18 20:27:31 +02:00
${ 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 \
2021-09-27 23:37:51 +02:00
" https:// ${ ALEXA } /api/np/player?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } ${ PARENT } " | ${ JQ } '.'
2018-06-18 20:27:31 +02:00
${ 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 \
2021-09-27 23:37:51 +02:00
" https:// ${ ALEXA } /api/media/state?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } " | ${ JQ } '.'
2018-06-18 20:27:31 +02:00
${ 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 \
2021-09-27 23:37:51 +02:00
" https:// ${ ALEXA } /api/np/queue?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } " | ${ JQ } '.'
2017-12-08 10:03:23 +01:00
}
2017-10-18 19:28:30 +02:00
2021-09-02 15:19:46 +02:00
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 \
2021-09-27 23:37:51 +02:00
" https:// ${ ALEXA } /api/behaviors/entities?skillId=amzn1.ask.1p.music " | ${ JQ } -r '.[] | select( .supportedProperties[] == "Alexa.Music.PlaySearchPhrase" ) | "\(.id) - \(.displayName) \(.description)"'
2021-09-02 15:19:46 +02:00
}
2021-05-27 22:51:01 +02:00
#
# device specific SPEAKVOL/NORMALVOL (sets SVOL/VOL)
#
get_volumes( )
{
VOL = ""
SVOL = ""
# Not using arrays here in order to be compatible with non-Bash
# Get the list position of the current device type
IDX = 0
for D in $DEVICEVOLNAME ; do
if [ " ${ D } " = " ${ DEVICE } " ] ; then
break;
fi
IDX = $(( IDX+1))
done
# get the speak volume at that position
C = 0
for D in $DEVICEVOLSPEAK ; do
if [ $C -eq $IDX ] ; then
if [ -n " ${ D } " ] ; then SVOL = $D ; fi
break
fi
C = $(( C+1))
done
if [ -z " ${ SVOL } " ] ; then
SVOL = $SPEAKVOL
fi
# try to retrieve the "currently playing" volume
VOLMAXAGE = 1
VOL = $( get_volume)
2021-09-15 16:18:10 +02:00
2021-05-27 22:51:01 +02:00
if [ -z " ${ VOL } " ] ; then
# get the normal volume of the current device type
C = 0
for D in $DEVICEVOLNORMAL ; do
if [ $C -eq $IDX ] ; then
VOL = $D
break
fi
C = $(( C+1))
done
# if the volume is still undefined, use $NORMALVOL
if [ -z " ${ VOL } " ] ; then
VOL = $NORMALVOL
fi
fi
}
2020-01-03 23:42:59 +01:00
#
# current volume level
#
get_volume( )
{
VOLFILE = $( find " ${ TMP } /.alexa.volume. ${ DEVICESERIALNUMBER } " -mmin -${ VOLMAXAGE } 2>/dev/null)
if [ -z " ${ VOLFILE } " ] ; then
VOL = $( ${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2021-01-28 23:35:48 +01:00
-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 \
2021-09-27 23:37:51 +02:00
" https:// ${ ALEXA } /api/devices/deviceType/dsn/audio/v1/allDeviceVolumes " | ${ JQ } -r --arg device " ${ DEVICESERIALNUMBER } " '.volumes[] | "\(.dsn) \(.speakerVolume) \(.speakerMuted)"' )
2020-01-03 23:42:59 +01:00
2020-02-09 01:54:07 +01:00
if [ -n " ${ VOL } " ] ; then
# write volume and mute state to file
OIFS = $IFS
IFS = '
2020-01-03 23:42:59 +01:00
'
2020-02-09 01:54:07 +01:00
set -o noglob
for LINE in $VOL ; do
SERIAL = $( echo " ${ LINE } " | cut -d' ' -f1)
VOLUME = $( echo " ${ LINE } " | cut -d' ' -f2)
MUTED = $( echo " ${ LINE } " | cut -d' ' -f3)
echo " ${ VOLUME } ${ MUTED } " > " ${ TMP } /.alexa.volume. ${ SERIAL } "
done
IFS = $OIFS
cut -d' ' -f1 " ${ TMP } /.alexa.volume. ${ DEVICESERIALNUMBER } "
fi
else
cut -d' ' -f1 " ${ TMP } /.alexa.volume. ${ DEVICESERIALNUMBER } "
2020-01-03 23:42:59 +01:00
fi
}
2019-08-25 17:18:04 +02:00
#
# show notifications and alarms
#
show_notifications( )
{
echo "/api/notifications"
${ 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/notifications?deviceSerialNumber= ${ DEVICESERIALNUMBER } &deviceType= ${ DEVICETYPE } "
echo
}
2017-12-08 10:03:23 +01:00
#
# deletes a multiroom device
#
delete_multiroom( )
{
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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 %, } ]} "
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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( )
{
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2018-01-10 08:24:36 +01:00
-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 \
2021-09-27 23:37:51 +02:00
" https:// ${ ALEXA } /api/bluetooth?cached=false " | ${ JQ } --arg serial " ${ DEVICESERIALNUMBER } " -r '.bluetoothStates[] | select(.deviceSerialNumber == $serial) | "\(.pairedDeviceList[]?.address) \(.pairedDeviceList[]?.friendlyName)"'
2018-01-10 08:24:36 +01:00
}
2017-12-08 10:03:23 +01:00
#
# connect bluetooth device
#
connect_bluetooth( )
{
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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( )
{
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
-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
2024-01-29 23:31:21 +01:00
#
# get activity CSRF token
#
get_activity_csrf( )
{
${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" \
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X GET\
" https://www. ${ AMAZON } /alexa-privacy/apd/activity?ref=activityHistory " | grep 'meta name="csrf-token" content="' | sed -r 's/^.*content="([^"]+)".*$/\1/g' > ${ TMP } /.alexa.activity.csrf
}
#
# get customer history records
#
get_history( )
{
if ! [ -f ${ TMP } /.alexa.activity.csrf ] ; then
get_activity_csrf
fi
RES = $( ${ CURL } ${ OPTS } -s -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L -w "%{http_code}" \
-H "Content-Type: application/json; charset=UTF-8" -H " anti-csrftoken-a2z: $( cat ${ TMP } /.alexa.activity.csrf) " \
-H " csrf: $( awk " \$0 ~/. ${ AMAZON } .*csrf[ \\s\\t]+/ {print \$7} " ${ COOKIE } ) " -X POST -d '{"previousRequestToken": null}' \
" https://www. ${ AMAZON } /alexa-privacy/apd/rvh/customer-history-records-v2/?startTime=0&endTime=2147483647000&pageType=VOICE_HISTORY " -o ${ TMP } /.alexa.activity.json)
# try again in case CSRF timed out
if [ $RES -ne 200 ] ; then
if [ -z " ${ try } " ] ; then
try = 1
rm -f ${ TMP } /.alexa.activity.csrf
get_history
else
echo "ERROR: unable to retrieve customer history records"
exit 1
fi
fi
}
2018-03-01 22:35:22 +01:00
#
# device that sent the last command
#
last_alexa( )
{
2024-01-29 23:31:21 +01:00
get_history
2024-02-06 11:40:20 +01:00
${ JQ } -r '.customerHistoryRecords | sort_by(.timestamp) | reverse | .[0] | .recordKey' ${ TMP } /.alexa.activity.json | cut -d'#' -f4 | xargs -i grep -m 1 { } ${ DEVLIST } .txt
2020-06-20 11:06:09 +02:00
}
#
# last command or last command of a specific device
#
last_command( )
{
2024-01-29 23:31:21 +01:00
get_history
if [ -z " $DEVICE " ] ; then
${ JQ } -r --arg device " $DEVICE " '.customerHistoryRecords | sort_by(.timestamp) | reverse | .[0] | .voiceHistoryRecordItems | map({key: .recordItemType, value: .transcriptText})' ${ TMP } /.alexa.activity.json
else
${ JQ } -r --arg device " $DEVICE " '[ .customerHistoryRecords | sort_by(.timestamp) | reverse | .[] | select( .device.deviceName == $device) ][0] | .voiceHistoryRecordItems | map({key: .recordItemType, value: .transcriptText})' ${ TMP } /.alexa.activity.json
fi
2020-06-20 11:06:09 +02:00
}
2018-03-01 22:35:22 +01:00
2017-10-18 19:28:30 +02:00
#
# logout
#
log_off( )
{
2018-01-28 18:33:16 +01:00
${ CURL } ${ OPTS } -s -c ${ COOKIE } -b ${ COOKIE } -A " ${ BROWSER } " -H "DNT: 1" -H "Connection: keep-alive" -L\
2017-12-08 10:03:23 +01:00
https://${ ALEXA } /logout > /dev/null
2017-10-18 19:28:30 +02:00
2021-09-15 16:18:10 +02:00
rm -f ${ DEVLIST } .json
rm -f ${ DEVLIST } .txt
rm -f ${ DEVLIST } _wha.txt
2017-10-18 19:28:30 +02:00
rm -f ${ COOKIE }
2017-12-08 10:03:23 +01:00
rm -f ${ TMP } /.alexa.*.list
2020-01-03 23:42:59 +01:00
rm -f ${ TMP } /.alexa.volume.*
2017-10-18 19:28:30 +02:00
}
2021-09-02 15:19:46 +02:00
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
2017-12-08 10:03:23 +01:00
echo "only logout option present, logging off ..."
log_off
exit 0
fi
if [ ! -f ${ COOKIE } ] ; then
2018-03-01 22:35:22 +01:00
echo "cookie does not exist. logging in ..."
2017-12-08 10:03:23 +01:00
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
2022-06-12 23:42:05 +02:00
if [ ! -f ${ DEVLIST } .json -o ! -f ${ DEVLIST } .txt ] ; then
2018-03-01 22:35:22 +01:00
echo "device list does not exist. downloading ..."
2017-12-20 18:55:59 +01:00
get_devlist
2021-09-15 16:18:10 +02:00
if [ ! -f ${ DEVLIST } .json ] ; then
2018-01-09 23:58:52 +01:00
echo "failed to download device list, aborting"
exit 1
fi
2017-12-20 18:55:59 +01:00
fi
2020-01-22 09:41:16 +01:00
if [ -n " $LOGIN " ] ; then
echo "logged in"
exit 0
fi
2021-09-02 15:19:46 +02:00
if [ -n " $CHANNEL " ] ; then
get_music_channels
exit 0
fi
2020-01-08 21:34:53 +01:00
if [ -n " $COMMAND " -o -n " $QUEUE " -o -n " $NOTIFICATIONS " -o -n " $GETVOL " ] ; then
2017-12-08 10:03:23 +01:00
if [ " ${ DEVICE } " = "ALL" ] ; then
2021-09-27 23:37:51 +02:00
for DEVICE in $( ${ JQ } -r '.devices[] | select( ( .deviceFamily == "ECHO" or .deviceFamily == "KNIGHT" or .deviceFamily == "ROOK" or .deviceFamily == "WHA" ) and .online == true ) | .accountName' ${ DEVLIST } .json | sed -r 's/ /%20/g' ) ; do
2017-12-08 10:03:23 +01:00
set_var
if [ -n " $COMMAND " ] ; then
2018-06-18 17:56:30 +02:00
echo " sending cmd: ${ COMMAND } to dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } customerid: ${ MEDIAOWNERCUSTOMERID } "
2017-12-08 10:03:23 +01:00
run_cmd
2019-02-10 20:04:53 +01:00
# in order to prevent a "Rate exceeded" we need to delay the command
sleep 1
2019-02-11 00:14:48 +01:00
echo
2020-01-03 23:42:59 +01:00
elif [ -n " $GETVOL " ] ; then
echo " get volume for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
get_volume
2020-01-08 21:34:53 +01:00
elif [ -n " $NOTIFICATIONS " ] ; then
echo " notifications info for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
show_notifications
2017-12-08 10:03:23 +01:00
else
echo " queue info for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
show_queue
2019-02-11 00:14:48 +01:00
echo
2017-12-08 10:03:23 +01:00
fi
done
else
set_var
if [ -n " $COMMAND " ] ; then
2018-06-18 17:56:30 +02:00
echo " sending cmd: ${ COMMAND } to dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } customerid: ${ MEDIAOWNERCUSTOMERID } "
2017-12-08 10:03:23 +01:00
run_cmd
2019-02-11 00:14:48 +01:00
echo
2020-01-03 23:42:59 +01:00
elif [ -n " $GETVOL " ] ; then
echo " get volume for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
get_volume
2020-01-08 21:34:53 +01:00
elif [ -n " $NOTIFICATIONS " ] ; then
echo " notifications info for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
show_notifications
2017-12-08 10:03:23 +01:00
else
echo " queue info for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } "
show_queue
2019-02-11 00:14:48 +01:00
echo
2017-12-08 10:03:23 +01:00
fi
fi
elif [ -n " $LEMUR " ] ; then
2021-09-27 23:37:51 +02:00
DEVICESERIALNUMBER = $( ${ JQ } --arg device " ${ LEMUR } " -r '.devices[] | select(.accountName == $device and .deviceFamily == "WHA") | .serialNumber' ${ DEVLIST } .json)
2017-12-08 10:03:23 +01:00
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
2019-02-11 00:14:48 +01:00
echo
2017-12-08 10:03:23 +01:00
fi
2021-09-15 16:18:10 +02:00
rm -f ${ DEVLIST } .json
rm -f ${ DEVLIST } .txt
rm -f ${ DEVLIST } _wha.txt
2017-12-08 10:03:23 +01:00
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
2021-09-27 23:37:51 +02:00
for DEVICE in $( ${ JQ } -r '.devices[] | select( .deviceFamily == "ECHO" or .deviceFamily == "KNIGHT" or .deviceFamily == "ROOK" or .deviceFamily == "WHA") | .accountName' ${ DEVLIST } .json | 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
2019-02-11 00:14:48 +01:00
echo
2018-01-10 08:24:36 +01:00
done
else
set_var
echo " bluetooth devices for dev: ${ DEVICE } type: ${ DEVICETYPE } serial: ${ DEVICESERIALNUMBER } : "
list_bluetooth
2019-02-11 00:14:48 +01:00
echo
2018-01-10 08:24:36 +01:00
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
2019-02-11 00:14:48 +01:00
echo
2017-12-08 10:03:23 +01:00
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
2019-02-11 00:14:48 +01:00
echo
2017-12-08 10:03:23 +01:00
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
2018-03-01 22:35:22 +01:00
elif [ -n " $LASTALEXA " ] ; then
last_alexa
2020-06-20 11:06:09 +02:00
elif [ -n " $LASTCOMMAND " ] ; then
last_command
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