diff --git a/README.md b/README.md index e45f90d..afb5f59 100755 --- a/README.md +++ b/README.md @@ -4,17 +4,12 @@ control Amazon Alexa from command Line The settings can now be controlled via environment variables. ``` -EMAIL - your login email -PASSWORD - your login password BROWSER - the User-Agent your browser sends in the request header -LANGUAGE - the Accept-Language your browser sends in the request header AMAZON - your Amazon domain ALEXA - the URL you would use for the Alexa Web App CURL - location of your cURL binary OPTS - any cURL options you require TMP - location of the temp dir -OATHTOOL - command line for oathtool MFA -MFA_SECRET- the MFA secret SPEAKVOL - the volume for speak messages ( if set to 0, volume levels are left untouched) NORMALVOL - if no current playing volume can be determined, fall back to normal volume VOLMAXAGE - max. age in minutes before volume is re-read from API @@ -22,11 +17,7 @@ DEVICEVOLNAME - a list of device names with specific volume settings (space se DEVICEVOLSPEAK - a list of speak volume levels - matching the devices above DEVICEVOLNORMAL - a list of normal volume levels- matching the devices above (current playing volume takes precedence for normal volume) -REFRESH_TOKEN - the new preference over EMAIL/PASSWORD can be obtained here: https://github.com/adn77/alexa-cookie-cli -``` -You will very likely want to set the language to: -``` -export LANGUAGE='de,en-US;q=0.7,en;q=0.3' +REFRESH_TOKEN - the new preference over EMAIL/PASSWORD can be obtained here: https://github.com/adn77/alexa-cookie-cli ``` ``` @@ -66,23 +57,8 @@ alexa-remote-control [-d |ALL] -e "` and enter that in the web form -1. Going from here the MFA shared secret becomes the MFA_SECRET for the alexa_remote_control script; -*Treat that MFA_SECRET just like your password - DO NOT share it anywhere!!!* - -It is assumed that MFA secured accounts are less likely to get a captcha response during login - that's why MFA might yield better results if the plain username/password didn't work for you. - -New option REFRESH_TOKEN ----- -The Alexa-App way of logging in is using a REFRESH_TOKEN which allows for obtaining the session cookies. This replaces EMAIL/PASSWORD/MFA so those will not be exposed in any scripts anymore. For convinience I created a binary, ready to run: https://github.com/adn77/alexa-cookie-cli +The Alexa-App way of logging in is using a REFRESH_TOKEN which allows for obtaining the session cookies. This replaces EMAIL/PASSWORD/MFA so those will not be exposed in any scripts anymore. For convenience I created a binary, ready to run: https://github.com/adn77/alexa-cookie-cli https://blog.loetzimmer.de/2021/09/alexa-remote-control-shell-script.html diff --git a/alexa_remote_control.sh b/alexa_remote_control.sh index 7275e79..ac92d1d 100755 --- a/alexa_remote_control.sh +++ b/alexa_remote_control.sh @@ -79,6 +79,9 @@ # -lastalexa now returns this string. Make sure to put the device in double quotes! # 2022-02-04: v0.20d minor volume fix (write volume to volume cache when volume is changed) # 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: 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 # ### # @@ -87,23 +90,13 @@ # - (GNU) sed and awk for extraction # - jq as command line JSON parser (optional for the fancy bits) # - base64 for B64 encoding (make sure "-w 0" option is available on your platform) -# - oathtool as OATH one-time password tool (optional for two-factor authentication) # ########################################## -SET_EMAIL='amazon_account@email.address' -SET_PASSWORD='Very_Secret_Amazon_Account_Password' -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/ +# e.g. from here: https://github.com/adn77/alexa-cookie-cli/ SET_REFRESH_TOKEN='' -SET_LANGUAGE='de,en-US;q=0.7,en;q=0.3' -#SET_LANGUAGE='en-US' - SET_TTS_LOCALE='de-DE' SET_AMAZON='amazon.de' @@ -126,9 +119,6 @@ SET_OPTS='--compressed --http1.1' SET_BROWSER='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:1.0) bash-script/1.0' #SET_BROWSER='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0' -# oathtool command line tool -SET_OATHTOOL='/usr/bin/oathtool' - # jq binary SET_JQ='/usr/bin/jq' @@ -156,19 +146,14 @@ SET_VOLMAXAGE="1" # # retrieving environment variables if any are set -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} BROWSER=${BROWSER:-$SET_BROWSER} CURL=${CURL:-$SET_CURL} 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} @@ -506,87 +491,44 @@ esac # log_in() { -################################################################ -# -# following headers are required: -# Accept-Language (possibly for determining login region) -# User-Agent (cURL wouldn't store cookies without) -# -################################################################ - rm -f ${DEVLIST}.json rm -f ${COOKIE} rm -f ${TMP}/.alexa.*.list 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" - - # - # 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" - - # 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 "${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" + 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|...'" 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} + BSD=$(uname | tr '[:upper:]' '[:lower:]' | grep -E 'darwin|bsd') + # workaround for cookies valid beyond 2038-01-19 on 32-bit systems toEpoch() { local x while read x do - echo "$x" | awk '{ - if ($3 >= 2038) { - print "s/"$1" "$2" "$3" "$4" "$5"/2147483647/g" - } else { - print "s/"$1" "$2" "$3" "$4" "$5"/'"$(date -d "$x" -u +"%s")"'/g" - } - }' + if [ -n "${BSD}" ] ; then + echo "$x" | awk '{ + if ($3 >= 2038) { + print "s/"$1" "$2" "$3" "$4" "$5"/2147483647/g" + } else { + print "s/"$1" "$2" "$3" "$4" "$5"/'"$(date -j -f "%d %b %Y %H:%M:%S %Z" "$x" +"%s")"'/g" + } + }' + else + echo "$x" | awk '{ + if ($3 >= 2038) { + print "s/"$1" "$2" "$3" "$4" "$5"/2147483647/g" + } else { + print "s/"$1" "$2" "$3" "$4" "$5"/'"$(date -d "$x" -u +"%s")"'/g" + } + }' + fi done } @@ -1189,34 +1131,65 @@ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep "https://${ALEXA}/api/bluetooth/disconnect-sink/${DEVICETYPE}/${DEVICESERIALNUMBER}" } +# +# 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 +} + # # device that sent the last command -# (by Markus Wennesheimer) # 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 grep -m 1 {} ${DEVLIST}.txt + get_history + ${JQ} -r '.customerHistoryRecords | sort_by(.timestamp) | reverse | .[0] | .device.deviceName' ${TMP}/.alexa.activity.json } # # last command or last command of a specific device -# (by Trinitus01) # last_command() { -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 -else - echo "$ACTIVITIES" | ${JQ} -r --arg serialnumber "$SERIALNUMBER" '[.activities[] | select( .activityStatus == "SUCCESS" ) | select( .sourceDeviceIds[].serialNumber == $serialnumber)][0] | .description' | ${JQ} -r .summary -fi + 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 } #