mirror of
				https://github.com/thorsten-gehrig/alexa-remote-control.git
				synced 2025-10-31 05:57:36 +01:00 
			
		
		
		
	fixed lastalexa/lastcommand
This commit is contained in:
		
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								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 <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 | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
| # | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Alexander Noack
					Alexander Noack