From 8309a07a16207271f1fb64968dc767c7a2547635 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 13 Sep 2021 23:14:24 +0200 Subject: [PATCH 1/5] Implemented refresh_token cookie exchange --- alexa_remote_control.sh | 104 +++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 44 deletions(-) diff --git a/alexa_remote_control.sh b/alexa_remote_control.sh index 4b9694c..bd4ad47 100755 --- a/alexa_remote_control.sh +++ b/alexa_remote_control.sh @@ -70,6 +70,8 @@ # 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: v0.20 implemented device registration refresh_token cookie exchange flow as an alternative +# to logging in # ### # @@ -88,6 +90,10 @@ SET_MFA_SECRET='' # something like: # 1234 5678 9ABC DEFG HIJK LMNO PQRS TUVW XYZ0 1234 5678 9ABC DEFG +# this can be obtained by doing the device registration login flow +# e.g. from here: https://github.com/Apollon77/alexa-cookie/ +SET_REFRESH_TOKEN='' + SET_LANGUAGE='de,en-US;q=0.7,en;q=0.3' #SET_LANGUAGE='en-US' @@ -143,6 +149,7 @@ SET_VOLMAXAGE="1" EMAIL=${EMAIL:-$SET_EMAIL} PASSWORD=${PASSWORD:-$SET_PASSWORD} MFA_SECRET=${MFA_SECRET:-$SET_MFA_SECRET} +REFRESH_TOKEN=${REFRESH_TOKEN:-$SET_REFRESH_TOKEN} AMAZON=${AMAZON:-$SET_AMAZON} ALEXA=${ALEXA:-$SET_ALEXA} LANGUAGE=${LANGUAGE:-$SET_LANGUAGE} @@ -231,7 +238,7 @@ usage() while [ "$#" -gt 0 ] ; do case "$1" in --version) - echo "v0.19" + echo "v0.20" exit 0 ;; -d) @@ -500,52 +507,67 @@ rm -f ${DEVLIST} rm -f ${COOKIE} rm -f ${TMP}/.alexa.*.list -# -# get first cookie and write redirection target into referer -# -${CURL} ${OPTS} -s -D "${TMP}/.alexa.header" -c ${COOKIE} -b ${COOKIE} -A "${BROWSER}" -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" +if [ -z "${REFRESH_TOKEN}" ] ; then + # + # get first cookie and write redirection target into referer + # + ${CURL} ${OPTS} -s -D "${TMP}/.alexa.header" -c ${COOKIE} -b ${COOKIE} -A "${BROWSER}" -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" -# -# login empty to generate session -# -${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A "${BROWSER}" -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" + # + # login empty to generate session + # + ${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A "${BROWSER}" -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" -# -# add OTP if using MFA -# -if [ -n "${MFA_SECRET}" ] ; then - OTP=$(${OATHTOOL} -b --totp "${MFA_SECRET}") - PASSWORD="${PASSWORD}${OTP}" -fi + # + # add OTP if using MFA + # + if [ -n "${MFA_SECRET}" ] ; then + OTP=$(${OATHTOOL} -b --totp "${MFA_SECRET}") + PASSWORD="${PASSWORD}${OTP}" + fi -# -# login with filled out form -# !!! referer now contains session in URL -# -${CURL} ${OPTS} -s -D "${TMP}/.alexa.header2" -c ${COOKIE} -b ${COOKIE} -A "${BROWSER}" -H "Accept-Language: ${LANGUAGE}" -H "DNT: 1" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" -L\ - -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 > "${TMP}/.alexa.login" + # + # login with filled out form + # !!! referer now contains session in URL + # + ${CURL} ${OPTS} -s -D "${TMP}/.alexa.header2" -c ${COOKIE} -b ${COOKIE} -A "${BROWSER}" -H "Accept-Language: ${LANGUAGE}" -H "DNT: 1" -H "Connection: keep-alive" -H "Upgrade-Insecure-Requests: 1" -L\ + -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 > "${TMP}/.alexa.login" -# check whether the login has been successful or exit otherwise -if [ -z "$(grep 'Location: https://alexa.*html' ${TMP}/.alexa.header2)" ] ; then - echo "ERROR: Amazon Login was unsuccessful. Possibly you get a captcha login screen." - echo " Try logging in to https://alexa.${AMAZON} with your browser. In your browser" - echo " make sure to have all Amazon related cookies deleted and Javascript disabled!" - echo - echo " (For more information have a look at ${TMP}/.alexa.login)" - echo - echo " To avoid issues with captcha, try using Multi-Factor Authentication." - echo " To do so, first set up Two-Step Verification on your Amazon account, then" - echo " configure this script (or the environment) with your MFA secret." - echo " Support for Multi-Factor Authentication requires 'oathtool' to be installed." + # check whether the login has been successful or exit otherwise + if [ -z "$(grep 'Location: https://alexa.*html' ${TMP}/.alexa.header2)" ] ; then + echo "ERROR: Amazon Login was unsuccessful. Possibly you get a captcha login screen." + echo " Try logging in to https://alexa.${AMAZON} with your browser. In your browser" + echo " make sure to have all Amazon related cookies deleted and Javascript disabled!" + echo + echo " (For more information have a look at ${TMP}/.alexa.login)" + echo + echo " To avoid issues with captcha, try using Multi-Factor Authentication." + echo " To do so, first set up Two-Step Verification on your Amazon account, then" + echo " configure this script (or the environment) with your MFA secret." + echo " Support for Multi-Factor Authentication requires 'oathtool' to be installed." - rm -f ${COOKIE} + rm -f ${COOKIE} + rm -f "${TMP}/.alexa.header" + rm -f "${TMP}/.alexa.header2" + rm -f "${TMP}/.alexa.postdata" + rm -f "${TMP}/.alexa.postdata2" + exit 1 + fi + + rm -f "${TMP}/.alexa.login" rm -f "${TMP}/.alexa.header" rm -f "${TMP}/.alexa.header2" rm -f "${TMP}/.alexa.postdata" rm -f "${TMP}/.alexa.postdata2" - exit 1 +else + ${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 '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime | strftime("%s") ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} + + if [ -z "$(grep ".${AMAZON}.*at-acbde" ${COOKIE})" ] ; then + echo "ERROR: cookie retrieval with refresh_token didn't work" + exit 1 + fi fi # @@ -569,12 +591,6 @@ if [ -z "$(grep ".${AMAZON}.*csrf" ${COOKIE})" ] ; then https://${ALEXA}/api/devices-v2/device?cached=false > /dev/null fi -rm -f "${TMP}/.alexa.login" -rm -f "${TMP}/.alexa.header" -rm -f "${TMP}/.alexa.header2" -rm -f "${TMP}/.alexa.postdata" -rm -f "${TMP}/.alexa.postdata2" - if [ -z "$(grep ".${AMAZON}.*csrf" ${COOKIE})" ] ; then echo "ERROR: no CSRF cookie received" exit 1 From 282a54d83b9cae89de5def0b312887b08531f10b Mon Sep 17 00:00:00 2001 From: Alexander Noack Date: Tue, 14 Sep 2021 10:20:25 +0200 Subject: [PATCH 2/5] fix date creation on 32bit systems --- alexa_remote_control.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/alexa_remote_control.sh b/alexa_remote_control.sh index bd4ad47..203bef5 100755 --- a/alexa_remote_control.sh +++ b/alexa_remote_control.sh @@ -562,12 +562,19 @@ if [ -z "${REFRESH_TOKEN}" ] ; then rm -f "${TMP}/.alexa.postdata" rm -f "${TMP}/.alexa.postdata2" else - ${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 '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime | strftime("%s") ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} + # ${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 '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} + + # work around for cookies valid beyond 2038-01-19 on 32bit systems + ${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 > ${COOKIE}.json + # replace expiration dates > 2037 before piping to JQ + sed -e "$(cat ${COOKIE}.json | jq -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | .Expires' | awk '$3 >= 2038 { print "s/"$1" "$2" "$3" "$4" "$5"/"$1" "$2" "2037" "$4" "$5"/g" ;}')" ${COOKIE}.json |\ + jq -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} if [ -z "$(grep ".${AMAZON}.*at-acbde" ${COOKIE})" ] ; then echo "ERROR: cookie retrieval with refresh_token didn't work" exit 1 fi + rm -f ${COOKIE}.json fi # From c4baa34b98771b578d6df4112db8affe8c2597e1 Mon Sep 17 00:00:00 2001 From: Alexander Noack Date: Wed, 15 Sep 2021 16:18:10 +0200 Subject: [PATCH 3/5] optimized speak commands to use less JQ --- alexa_remote_control.sh | 86 +++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/alexa_remote_control.sh b/alexa_remote_control.sh index 203bef5..dfd0f29 100755 --- a/alexa_remote_control.sh +++ b/alexa_remote_control.sh @@ -71,7 +71,8 @@ # 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: v0.20 implemented device registration refresh_token cookie exchange flow as an alternative -# to logging in +# to logging in +# 2021-09-15: v0.20a optimized speak commands to use less JQ. This is useful in low-resource environments # ### # @@ -167,7 +168,7 @@ DEVICEVOLSPEAK=${DEVICEVOLSPEAK:-$SET_DEVICEVOLSPEAK} DEVICEVOLNORMAL=${DEVICEVOLNORMAL:-$SET_DEVICEVOLNORMAL} COOKIE="${TMP}/.alexa.cookie" -DEVLIST="${TMP}/.alexa.devicelist.json" +DEVLIST="${TMP}/.alexa.devicelist" GUIVERSION=0 @@ -238,7 +239,7 @@ usage() while [ "$#" -gt 0 ] ; do case "$1" in --version) - echo "v0.20" + echo "v0.20a" exit 0 ;; -d) @@ -503,7 +504,7 @@ log_in() # ################################################################ -rm -f ${DEVLIST} +rm -f ${DEVLIST}.json rm -f ${COOKIE} rm -f ${TMP}/.alexa.*.list @@ -562,19 +563,18 @@ if [ -z "${REFRESH_TOKEN}" ] ; then rm -f "${TMP}/.alexa.postdata" rm -f "${TMP}/.alexa.postdata2" else - # ${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 '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} +# ${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 '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} # work around for cookies valid beyond 2038-01-19 on 32bit systems - ${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 > ${COOKIE}.json - # replace expiration dates > 2037 before piping to JQ - sed -e "$(cat ${COOKIE}.json | jq -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | .Expires' | awk '$3 >= 2038 { print "s/"$1" "$2" "$3" "$4" "$5"/"$1" "$2" "2037" "$4" "$5"/g" ;}')" ${COOKIE}.json |\ - jq -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} + ${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 > ${COOKIE}.json + sed -e "$(cat ${COOKIE}.json | jq -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | .Expires' | awk '$3 >= 2038 { print "s/"$1" "$2" "$3" "$4" "$5"/"$1" "$2" "2037" "$4" "$5"/g" ;}')" ${COOKIE}.json |\ + jq -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} if [ -z "$(grep ".${AMAZON}.*at-acbde" ${COOKIE})" ] ; then echo "ERROR: cookie retrieval with refresh_token didn't work" exit 1 fi - rm -f ${COOKIE}.json + rm -rf ${COOKIE}.json fi # @@ -609,10 +609,13 @@ fi # get_devlist() { -${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} + ${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 + + jq -r '.devices[] | "\(.accountName)=\(.deviceType)=\(.serialNumber)=\(.deviceFamily)"' ${DEVLIST}.json > ${DEVLIST}.txt + jq -r '.devices[] | select(.deviceFamily == "WHA") | "\(.accountName)=\(.clusterMembers[])"' ${DEVLIST}.json > ${DEVLIST}_wha.txt } check_status() @@ -641,18 +644,21 @@ set_var() if [ -z "${DEVICE}" ] ; then # if no device was supplied, use the first Echo(dot) in device list - echo "setting default device to:" - DEVICE=$(jq -r '[ .devices[] | select(.deviceFamily == "ECHO" or .deviceFamily == "KNIGHT" or .deviceFamily == "ROOK" ) | .accountName] | .[0]' ${DEVLIST}) + echo -n "setting default device to: " + DEVICE=$(grep -m 1 -E "ECHO|KNIGHT|ROOK" ${DEVLIST}.txt | cut -d'=' -f1) 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}) - DEVICEFAMILY=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .deviceFamily' ${DEVLIST}) + DEVICESERIALNUMBER=$(grep -m 1 "${DEVICE}" ${DEVLIST}.txt) + DEVICESERIALNUMBER=${DEVICESERIALNUMBER#*=} + DEVICEFAMILY=${DEVICESERIALNUMBER##*=} + DEVICETYPE=${DEVICESERIALNUMBER%%=*} + DEVICESERIALNUMBER=${DEVICESERIALNUMBER#*=} + DEVICESERIALNUMBER=${DEVICESERIALNUMBER%=*} # customerId is now retrieved from the logged in user # the customerId in the device list is always from the user registering the device initially - # MEDIAOWNERCUSTOMERID=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .deviceOwnerCustomerId' ${DEVLIST}) + # MEDIAOWNERCUSTOMERID=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .deviceOwnerCustomerId' ${DEVLIST}.json) if [ -z "${DEVICESERIALNUMBER}" ] ; then echo "ERROR: unkown device dev:${DEVICE}" @@ -665,7 +671,7 @@ set_var() # list_devices() { - jq -r '.devices[].accountName' ${DEVLIST} + jq -r '.devices[].accountName' ${DEVLIST}.json } # @@ -755,15 +761,15 @@ if [ -n "${SEQUENCECMD}" ] ; then # 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 - MEMBERDEVICESERIALS=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .clusterMembers[]' ${DEVLIST}) + MEMBERDEVICESERIALS=$(grep "${DEVICE}" ${DEVLIST}_wha.txt | cut -d'=' -f 2) for DEVICESERIALNUMBER in $MEMBERDEVICESERIALS ; do - DEVICETYPE=$(jq --arg device "${DEVICESERIALNUMBER}" -r '.devices[] | select(.serialNumber == $device) | .deviceType' ${DEVLIST}) + DEVICETYPE=$(grep "${DEVICESERIALNUMBER}" ${DEVLIST}.txt | cut -d'=' -f 2) NODESTOEXECUTE=$(add_node "$(node)" "${NODESTOEXECUTE}") # add volume setting per device - the WHA volume is unrelyable # 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=$(grep "${DEVICESERIALNUMBER}" ${DEVLIST}.txt | cut -d'=' -f 1) get_volumes VOLUMEPRENODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\"value\":\"'${SVOL}'\"') ${VOLUMEPRENODESTOEXECUTE}) VOLUMEPOSTNODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\"value\":\"'${VOL}'\"') ${VOLUMEPOSTNODESTOEXECUTE}) @@ -950,12 +956,12 @@ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep show_queue() { PARENT="" - PARENTID=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .parentClusters[0]' ${DEVLIST}) + PARENTID=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .parentClusters[0]' ${DEVLIST}.json) if [ "$PARENTID" != "null" ] ; then - PARENTDEVICE=$(jq --arg serial ${PARENTID} -r '.devices[] | select(.serialNumber == $serial) | .deviceType' ${DEVLIST}) + PARENTDEVICE=$(jq --arg serial ${PARENTID} -r '.devices[] | select(.serialNumber == $serial) | .deviceType' ${DEVLIST}.json) PARENT="&lemurId=${PARENTID}&lemurDeviceType=${PARENTDEVICE}" 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 GET \ @@ -1014,7 +1020,7 @@ get_volumes() # try to retrieve the "currently playing" volume VOLMAXAGE=1 VOL=$(get_volume) - + if [ -z "${VOL}" ] ; then # get the normal volume of the current device type C=0 @@ -1149,9 +1155,9 @@ last_alexa() ${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/activities?startTime=&size=10&offset=1" | jq -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' | xargs -i jq -r --arg device {} '.devices[] | select( .serialNumber == $device) | .accountName' ${DEVLIST} + "https://${ALEXA}/api/activities?startTime=&size=10&offset=1" | jq -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' | xargs -i jq -r --arg device {} '.devices[] | select( .serialNumber == $device) | .accountName' ${DEVLIST}.json # Serial number: | jq -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' -# Device name: | jq -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' | xargs -i jq -r --arg device {} '.devices[] | select( .serialNumber == $device) | .accountName' ${DEVLIST} +# Device name: | jq -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' | xargs -i jq -r --arg device {} '.devices[] | select( .serialNumber == $device) | .accountName' ${DEVLIST}.json } # @@ -1160,7 +1166,7 @@ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep # last_command() { -SERIALNUMBER=$(jq -r --arg device "$DEVICE" '.devices[] | select( .accountName == $device ) | .serialNumber' ${DEVLIST}) +SERIALNUMBER=$(jq -r --arg device "$DEVICE" '.devices[] | select( .accountName == $device ) | .serialNumber' ${DEVLIST}.json) ACTIVITIES=$(${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\ @@ -1180,7 +1186,9 @@ log_off() ${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep-alive" -L\ https://${ALEXA}/logout > /dev/null -rm -f ${DEVLIST} +rm -f ${DEVLIST}.json +rm -f ${DEVLIST}.txt +rm -f ${DEVLIST}_wha.txt rm -f ${COOKIE} rm -f ${TMP}/.alexa.*.list rm -f ${TMP}/.alexa.volume.* @@ -1208,10 +1216,10 @@ if [ $? -eq 0 ] ; then fi fi -if [ ! -f ${DEVLIST} ] ; then +if [ ! -f ${DEVLIST}.json ] ; then echo "device list does not exist. downloading ..." get_devlist - if [ ! -f ${DEVLIST} ] ; then + if [ ! -f ${DEVLIST}.json ] ; then echo "failed to download device list, aborting" exit 1 fi @@ -1229,7 +1237,7 @@ fi if [ -n "$COMMAND" -o -n "$QUEUE" -o -n "$NOTIFICATIONS" -o -n "$GETVOL" ] ; 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}.json | sed -r 's/ /%20/g') ; do set_var if [ -n "$COMMAND" ] ; then echo "sending cmd:${COMMAND} to dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} customerid:${MEDIAOWNERCUSTOMERID}" @@ -1268,7 +1276,7 @@ if [ -n "$COMMAND" -o -n "$QUEUE" -o -n "$NOTIFICATIONS" -o -n "$GETVOL" ] ; the fi fi elif [ -n "$LEMUR" ] ; then - DEVICESERIALNUMBER=$(jq --arg device "${LEMUR}" -r '.devices[] | select(.accountName == $device and .deviceFamily == "WHA") | .serialNumber' ${DEVLIST}) + DEVICESERIALNUMBER=$(jq --arg device "${LEMUR}" -r '.devices[] | select(.accountName == $device and .deviceFamily == "WHA") | .serialNumber' ${DEVLIST}.json) if [ -n "$DEVICESERIALNUMBER" ] ; then delete_multiroom else @@ -1284,12 +1292,14 @@ elif [ -n "$LEMUR" ] ; then create_multiroom echo fi - rm -f ${DEVLIST} + rm -f ${DEVLIST}.json + rm -f ${DEVLIST}.txt + rm -f ${DEVLIST}_wha.txt get_devlist elif [ -n "$BLUETOOTH" ] ; then if [ "$BLUETOOTH" = "list" -o "$BLUETOOTH" = "List" -o "$BLUETOOTH" = "LIST" ] ; then if [ "${DEVICE}" = "ALL" ] ; then - 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 + 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 set_var echo "bluetooth devices for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}:" list_bluetooth From 76e978f534b448be1ad6bbdb803cb3d3fcdd697e Mon Sep 17 00:00:00 2001 From: Daniel Mann Date: Mon, 27 Sep 2021 23:37:51 +0200 Subject: [PATCH 4/5] Add environment variable for jq so the binary path of jq can be overwritten in a wrapper script via "export JQ='/some/path/jq" --- alexa_remote_control.sh | 76 ++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/alexa_remote_control.sh b/alexa_remote_control.sh index dfd0f29..4812a07 100755 --- a/alexa_remote_control.sh +++ b/alexa_remote_control.sh @@ -123,6 +123,9 @@ SET_BROWSER='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:1.0) bash-script/1.0' # oathtool command line tool SET_OATHTOOL='/usr/bin/oathtool' +# jq binary +SET_JQ='/usr/bin/jq' + # tmp path SET_TMP="/tmp" @@ -160,6 +163,7 @@ OPTS=${OPTS:-$SET_OPTS} TTS_LOCALE=${TTS_LOCALE:-$SET_TTS_LOCALE} TMP=${TMP:-$SET_TMP} OATHTOOL=${OATHTOOL:-$SET_OATHTOOL} +JQ=${JQ:-$SET_JQ} SPEAKVOL=${SPEAKVOL:-$SET_SPEAKVOL} NORMALVOL=${NORMALVOL:-$SET_NORMALVOL} VOLMAXAGE=${VOLMAXAGE:-$SET_VOLMAXAGE} @@ -292,7 +296,7 @@ while [ "$#" -gt 0 ] ; do # 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 # 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') + 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 @@ -563,12 +567,12 @@ if [ -z "${REFRESH_TOKEN}" ] ; then rm -f "${TMP}/.alexa.postdata" rm -f "${TMP}/.alexa.postdata2" else -# ${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 '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} +# ${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 '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} # work around for cookies valid beyond 2038-01-19 on 32bit systems ${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 > ${COOKIE}.json - sed -e "$(cat ${COOKIE}.json | jq -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | .Expires' | awk '$3 >= 2038 { print "s/"$1" "$2" "$3" "$4" "$5"/"$1" "$2" "2037" "$4" "$5"/g" ;}')" ${COOKIE}.json |\ - jq -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} + sed -e "$(cat ${COOKIE}.json | ${JQ} -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | .Expires' | awk '$3 >= 2038 { print "s/"$1" "$2" "$3" "$4" "$5"/"$1" "$2" "2037" "$4" "$5"/g" ;}')" ${COOKIE}.json |\ + ${JQ} -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} if [ -z "$(grep ".${AMAZON}.*at-acbde" ${COOKIE})" ] ; then echo "ERROR: cookie retrieval with refresh_token didn't work" @@ -614,8 +618,8 @@ get_devlist() -H "csrf: $(awk "\$0 ~/.${AMAZON}.*csrf[ \\s\\t]+/ {print \$7}" ${COOKIE})"\ "https://${ALEXA}/api/devices-v2/device?cached=false" > ${DEVLIST}.json - jq -r '.devices[] | "\(.accountName)=\(.deviceType)=\(.serialNumber)=\(.deviceFamily)"' ${DEVLIST}.json > ${DEVLIST}.txt - jq -r '.devices[] | select(.deviceFamily == "WHA") | "\(.accountName)=\(.clusterMembers[])"' ${DEVLIST}.json > ${DEVLIST}_wha.txt + ${JQ} -r '.devices[] | "\(.accountName)=\(.deviceType)=\(.serialNumber)=\(.deviceFamily)"' ${DEVLIST}.json > ${DEVLIST}.txt + ${JQ} -r '.devices[] | select(.deviceFamily == "WHA") | "\(.accountName)=\(.clusterMembers[])"' ${DEVLIST}.json > ${DEVLIST}_wha.txt } check_status() @@ -658,7 +662,7 @@ set_var() DEVICESERIALNUMBER=${DEVICESERIALNUMBER%=*} # customerId is now retrieved from the logged in user # the customerId in the device list is always from the user registering the device initially - # MEDIAOWNERCUSTOMERID=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .deviceOwnerCustomerId' ${DEVLIST}.json) + # MEDIAOWNERCUSTOMERID=$(${JQ} --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .deviceOwnerCustomerId' ${DEVLIST}.json) if [ -z "${DEVICESERIALNUMBER}" ] ; then echo "ERROR: unkown device dev:${DEVICE}" @@ -671,7 +675,7 @@ set_var() # list_devices() { - jq -r '.devices[].accountName' ${DEVLIST}.json + ${JQ} -r '.devices[].accountName' ${DEVLIST}.json } # @@ -691,7 +695,7 @@ sanitize_search() ${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' + "https://${ALEXA}/api/behaviors/operation/validate" | ${JQ} -r '.operationPayload.sanitizedSearchPhrase' } # @@ -735,16 +739,16 @@ if [ -n "${SEQUENCECMD}" ] ; then -H "csrf: $(awk "\$0 ~/.${AMAZON}.*csrf[ \\s\\t]+/ {print \$7}" ${COOKIE})" -X GET \ "https://${ALEXA}/api/behaviors/v2/automations?limit=200" > "${TMP}/.alexa.automation" - AUTOMATION=$(jq --arg utterance "${UTTERANCE}" -r '.[] | select( .triggers[].payload.utterance == $utterance) | .automationId' "${TMP}/.alexa.automation") + AUTOMATION=$(${JQ} --arg utterance "${UTTERANCE}" -r '.[] | select( .triggers[].payload.utterance == $utterance) | .automationId' "${TMP}/.alexa.automation") if [ -z "${AUTOMATION}" ] ; then - AUTOMATION=$(jq --arg utterance "${UTTERANCE}" -r '.[] | select( .name == $utterance) | .automationId' "${TMP}/.alexa.automation") + AUTOMATION=$(${JQ} --arg utterance "${UTTERANCE}" -r '.[] | select( .name == $utterance) | .automationId' "${TMP}/.alexa.automation") if [ -z "${AUTOMATION}" ] ; then echo "ERROR: no such utterance '${UTTERANCE}' in Alexa routines" rm -f "${TMP}/.alexa.automation" exit 1 fi fi - 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") + 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") rm -f "${TMP}/.alexa.automation" ALEXACMD='{"behaviorId":"'${AUTOMATION}'","sequenceJson":"'${SEQUENCE}'","status":"ENABLED"}' @@ -912,17 +916,17 @@ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep -H "csrf: $(awk "\$0 ~/.${AMAZON}.*csrf[ \\s\\t]+/ {print \$7}" ${COOKIE})" -X GET \ "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} + 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} + ${JQ} -r '.playlist.trackCount' ${FILE} + ${JQ} '.playlist.entryList[] | .[]' ${FILE} } # @@ -939,7 +943,7 @@ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep "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 + for I in $(${JQ} -r '.primePlaylistBrowseNodeList[].subNodes[].nodeId' ${FILE} 2>/dev/null) ; do ${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 \ @@ -947,7 +951,7 @@ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep done fi fi - jq '.' ${FILE} + ${JQ} '.' ${FILE} } # @@ -956,26 +960,26 @@ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep show_queue() { PARENT="" - PARENTID=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .parentClusters[0]' ${DEVLIST}.json) + PARENTID=$(${JQ} --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .parentClusters[0]' ${DEVLIST}.json) if [ "$PARENTID" != "null" ] ; then - PARENTDEVICE=$(jq --arg serial ${PARENTID} -r '.devices[] | select(.serialNumber == $serial) | .deviceType' ${DEVLIST}.json) + PARENTDEVICE=$(${JQ} --arg serial ${PARENTID} -r '.devices[] | select(.serialNumber == $serial) | .deviceType' ${DEVLIST}.json) PARENT="&lemurId=${PARENTID}&lemurDeviceType=${PARENTDEVICE}" 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 GET \ - "https://${ALEXA}/api/np/player?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}${PARENT}" | jq '.' + "https://${ALEXA}/api/np/player?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}${PARENT}" | ${JQ} '.' ${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/media/state?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}" | jq '.' + "https://${ALEXA}/api/media/state?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}" | ${JQ} '.' ${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/np/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}" | jq '.' + "https://${ALEXA}/api/np/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}" | ${JQ} '.' } get_music_channels() @@ -983,7 +987,7 @@ 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)"' + "https://${ALEXA}/api/behaviors/entities?skillId=amzn1.ask.1p.music" | ${JQ} -r '.[] | select( .supportedProperties[] == "Alexa.Music.PlaySearchPhrase" ) | "\(.id) - \(.displayName) \(.description)"' } # @@ -1049,7 +1053,7 @@ get_volume() VOL=$(${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/devices/deviceType/dsn/audio/v1/allDeviceVolumes" | jq -r --arg device "${DEVICESERIALNUMBER}" '.volumes[] | "\(.dsn) \(.speakerVolume) \(.speakerMuted)"') + "https://${ALEXA}/api/devices/deviceType/dsn/audio/v1/allDeviceVolumes" | ${JQ} -r --arg device "${DEVICESERIALNUMBER}" '.volumes[] | "\(.dsn) \(.speakerVolume) \(.speakerMuted)"') if [ -n "${VOL}" ] ; then # write volume and mute state to file @@ -1121,7 +1125,7 @@ list_bluetooth() ${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/bluetooth?cached=false" | jq --arg serial "${DEVICESERIALNUMBER}" -r '.bluetoothStates[] | select(.deviceSerialNumber == $serial) | "\(.pairedDeviceList[]?.address) \(.pairedDeviceList[]?.friendlyName)"' + "https://${ALEXA}/api/bluetooth?cached=false" | ${JQ} --arg serial "${DEVICESERIALNUMBER}" -r '.bluetoothStates[] | select(.deviceSerialNumber == $serial) | "\(.pairedDeviceList[]?.address) \(.pairedDeviceList[]?.friendlyName)"' } # @@ -1155,9 +1159,9 @@ last_alexa() ${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/activities?startTime=&size=10&offset=1" | jq -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' | xargs -i jq -r --arg device {} '.devices[] | select( .serialNumber == $device) | .accountName' ${DEVLIST}.json -# Serial number: | jq -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' -# Device name: | jq -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' | xargs -i jq -r --arg device {} '.devices[] | select( .serialNumber == $device) | .accountName' ${DEVLIST}.json + "https://${ALEXA}/api/activities?startTime=&size=10&offset=1" | ${JQ} -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' | xargs -i ${JQ} -r --arg device {} '.devices[] | select( .serialNumber == $device) | .accountName' ${DEVLIST}.json +# Serial number: | ${JQ} -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' +# Device name: | ${JQ} -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .sourceDeviceIds[0].serialNumber' | xargs -i ${JQ} -r --arg device {} '.devices[] | select( .serialNumber == $device) | .accountName' ${DEVLIST}.json } # @@ -1166,15 +1170,15 @@ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep # last_command() { -SERIALNUMBER=$(jq -r --arg device "$DEVICE" '.devices[] | select( .accountName == $device ) | .serialNumber' ${DEVLIST}.json) +SERIALNUMBER=$(${JQ} -r --arg device "$DEVICE" '.devices[] | select( .accountName == $device ) | .serialNumber' ${DEVLIST}.json) ACTIVITIES=$(${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/activities?startTime=&size=10&offset=1") if [ -z "$DEVICE" ] ; then - echo "$ACTIVITIES" | jq -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .description' | jq -r .summary + echo "$ACTIVITIES" | ${JQ} -r '[.activities[] | select( .activityStatus == "SUCCESS" )][0] | .description' | ${JQ} -r .summary else - echo "$ACTIVITIES" | jq -r --arg serialnumber "$SERIALNUMBER" '[.activities[] | select( .activityStatus == "SUCCESS" ) | select( .sourceDeviceIds[].serialNumber == $serialnumber)][0] | .description' | jq -r .summary + echo "$ACTIVITIES" | ${JQ} -r --arg serialnumber "$SERIALNUMBER" '[.activities[] | select( .activityStatus == "SUCCESS" ) | select( .sourceDeviceIds[].serialNumber == $serialnumber)][0] | .description' | ${JQ} -r .summary fi } @@ -1237,7 +1241,7 @@ fi if [ -n "$COMMAND" -o -n "$QUEUE" -o -n "$NOTIFICATIONS" -o -n "$GETVOL" ] ; 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}.json | 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}.json | sed -r 's/ /%20/g') ; do set_var if [ -n "$COMMAND" ] ; then echo "sending cmd:${COMMAND} to dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} customerid:${MEDIAOWNERCUSTOMERID}" @@ -1276,7 +1280,7 @@ if [ -n "$COMMAND" -o -n "$QUEUE" -o -n "$NOTIFICATIONS" -o -n "$GETVOL" ] ; the fi fi elif [ -n "$LEMUR" ] ; then - DEVICESERIALNUMBER=$(jq --arg device "${LEMUR}" -r '.devices[] | select(.accountName == $device and .deviceFamily == "WHA") | .serialNumber' ${DEVLIST}.json) + DEVICESERIALNUMBER=$(${JQ} --arg device "${LEMUR}" -r '.devices[] | select(.accountName == $device and .deviceFamily == "WHA") | .serialNumber' ${DEVLIST}.json) if [ -n "$DEVICESERIALNUMBER" ] ; then delete_multiroom else @@ -1299,7 +1303,7 @@ elif [ -n "$LEMUR" ] ; then elif [ -n "$BLUETOOTH" ] ; then if [ "$BLUETOOTH" = "list" -o "$BLUETOOTH" = "List" -o "$BLUETOOTH" = "LIST" ] ; then if [ "${DEVICE}" = "ALL" ] ; then - 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 + 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 set_var echo "bluetooth devices for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}:" list_bluetooth From 60eae2eeda80c101541d89bb0d125a0aa0fa9842 Mon Sep 17 00:00:00 2001 From: Alexander Noack Date: Thu, 7 Oct 2021 09:05:00 +0200 Subject: [PATCH 5/5] fixed different cookie naming for amazon.com --- alexa_remote_control.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/alexa_remote_control.sh b/alexa_remote_control.sh index 4812a07..e8b4016 100755 --- a/alexa_remote_control.sh +++ b/alexa_remote_control.sh @@ -73,6 +73,7 @@ # 2021-09-13: v0.20 implemented device registration refresh_token cookie exchange flow as an alternative # 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: v0.20b fixed different cookie naming for amazon.com # ### # @@ -243,7 +244,7 @@ usage() while [ "$#" -gt 0 ] ; do case "$1" in --version) - echo "v0.20a" + echo "v0.20b" exit 0 ;; -d) @@ -574,7 +575,7 @@ else sed -e "$(cat ${COOKIE}.json | ${JQ} -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | .Expires' | awk '$3 >= 2038 { print "s/"$1" "$2" "$3" "$4" "$5"/"$1" "$2" "2037" "$4" "$5"/g" ;}')" ${COOKIE}.json |\ ${JQ} -r '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then "TRUE" elif . == false then "FALSE" else . end) | .Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | [(if .HttpOnly=="TRUE" then ("#HttpOnly_" + $domain) else $domain end), "TRUE", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE} - if [ -z "$(grep ".${AMAZON}.*at-acbde" ${COOKIE})" ] ; then + if [ -z "$(grep "\.${AMAZON}.*\sat-" ${COOKIE})" ] ; then echo "ERROR: cookie retrieval with refresh_token didn't work" exit 1 fi @@ -588,21 +589,21 @@ ${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Con -H "Referer: https://alexa.${AMAZON}/spa/index.html" -H "Origin: https://alexa.${AMAZON}"\ https://${ALEXA}/api/language > /dev/null -if [ -z "$(grep ".${AMAZON}.*csrf" ${COOKIE})" ] ; then +if [ -z "$(grep "\.${AMAZON}.*\scsrf" ${COOKIE})" ] ; then 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 -if [ -z "$(grep ".${AMAZON}.*csrf" ${COOKIE})" ] ; then +if [ -z "$(grep "\.${AMAZON}.*\scsrf" ${COOKIE})" ] ; then 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 -if [ -z "$(grep ".${AMAZON}.*csrf" ${COOKIE})" ] ; then +if [ -z "$(grep "\.${AMAZON}.*\scsrf" ${COOKIE})" ] ; then echo "ERROR: no CSRF cookie received" exit 1 fi