fixed lastalexa/lastcommand

This commit is contained in:
Alexander Noack 2024-01-29 23:31:21 +01:00
parent 8f86f40ee4
commit 4235eb66c2
2 changed files with 79 additions and 130 deletions

View File

@ -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 <device>|ALL] -e <pause|play|next|prev|fwd|rwd|shuffle|
-h : help
```
There's also a (NOW DEPRECATED) "plain" version, which lacks some functionality (-z, -i, -p, -P, -S and no radio station names and no routines) but doesn't require 'jq' for JSON processing.
Old option MFA
Login via REFRESH_TOKEN
----
In order to use MFA, one needs to obtain the MFA_SECRET from Amazon account:
1. You should have MFA using an App already working before proceeding
1. Add a new app
1. When presented with the QR-code select "can't scan code"
1. You will be presented with the MFA shared secret, something like `1234 5678 9ABC DEFG HIJK LMNO PQRS TUVW XYZ0 1234 5678 9ABC DEFG`
1. Now you have to generate a valid response code via `oathtool -b --totp "<MFA shared secret from above>"` 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

View File

@ -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
}
#