Montag, 20. September 2021

Alexa Remote Control Shell Script

 It's about time for a new post since a lot of things have changed over the course of the past four years.

First things first though, the good news is there's finally an almost official way to properly use the script with your Amazon Alexa. Luckily all major home automation projects became inspired by the idea of the original script and some inner workings have been discovered by people smarter than me. Michael Geramb of openHAB and Ingo Fischer of ioBroker came up with the idea to proxy the login to Amazon very similar to how a mobile app would.

The result was for example the nodeJs package alexa-cookie2. Looking more closely at the result of the login process it became clear that only a single item is required for our purposes. Hence I created a small nodeJS app that will output the token after successfully completing the login.

As not everybody would want to install nodeJS (or run it in Docker) I created packaged binaries using the nodeJS packager pkg.

While the old login flow may still work (for some time) I strongly encourage everybody to try the new cleaner method.

You will have to do this only once - very likely not on the actual machine that is going to run the alexa_remote_control.sh script, but on your browser enabled workstation!

  1. Start the alexa-cookie-cli-<linux|macos|windows> on your workstation from the command-line
    (cli is important as the window might close otherwise!!!)
  2. Point your browser at http://localhost:8080/
  3. Login to Amazon
  4. Upon successful login the program will exit and return the refresh_token to the command-line output (something like Atnr|...)

This will be the value of the REFRESH_TOKEN environment variable in the alexa_remote_control.sh script. No need to store your precious password anymore!

There are some options that alter the behavior if you are not an amazon.de user. Run the binary with --help to see.

Quite a few changes might have gone unnoticed. One of the most important is the use of environment variables. While you can always edit the script (as I originally intended) and modify the settings there, every SET_ variable has it's counterpart in an environment variable (without the SET_ prefix). 

I am going to demonstrate that behavior by using a feature which also seems not to be well understood. There is individual volume level control by Echo device names. Assume you have three Echos:

DEVICEVOLNAME='Kitchen LivingRoom BedRoom'

and you want the TTS volumes to be different:

DEVICEVOLSPEAK='100 80 25'
(the list of volumes refers to each of the Echo devices)

By creating a wrapper script around alexa_remote_control.sh you could even do something like:

#!/bin/sh
# alexa.sh wrapper script
#
export REFRESH_TOKEN='Atnr|...'
export DEVICEVOLNAME='Kitchen LivingRoom BedRoom'

# hush, hush during evening/night time
H=$(date +%H)
if [ $H -ge 19 -o $H -le 7 ] ; then
        export DEVICEVOLSPEAK="80 50 12"
        export DEVICEVOLNORMAL="70 40 9"
else
        export DEVICEVOLSPEAK="100 80 25"
        export DEVICEVOLNORMAL="80 50 12"
fi

/some/path/alexa_remote_control.sh "$@"

40 Kommentare:

  1. Awesome! Gotta try it out asap, thanks for continuous work you put into this :)

    AntwortenLöschen
  2. Hello,
    I would like to use alexa_remote_control.sh in a Rpi4 working in headless mode.

    How can I get the token? Please help!

    Thanks a lot,
    Dan

    AntwortenLöschen
    Antworten
    1. I couldn't figure out how to build linux_arm on Github.
      As you need this procedure only a single time, just run it on your x86 PC or Mac and follow steps 1.-4.

      Löschen
  3. Ich kann auch keinen Token erhalten. Fehlermeldung bei der WIN Version:
    Error: You can try to get the cookie manually by opening http://localhost_8080/ with your browser. / null
    Denke eine Ursache könnte sein , dass localhost:8080 in meinem Netzwerk nicht funktioniert. Gibt's noch eine andere Möglichkeit um an den Token zu kommen?

    AntwortenLöschen
    Antworten
    1. alexa-cookie-cli-win-x64.exe --help
      Die "Fehlermeldung" stammt aus dem originalen Code, darauf habe ich keinen Einfluss...
      Wenn der Port 8080 bei dir belegt ist, kannst du mit "-P 8081" den Port auch umdefinieren.

      Löschen
  4. Mich würde mal interessieren, wie ich diese Datei auf meinem Raspi ausführen kann. Eine kleine Schritt für Schritt Anleitung wäre super.
    Welche Datei brauche ich überhaupt. Ich habe einen Raspi mit Homebridge am laufen.

    AntwortenLöschen
    Antworten
    1. Naja, du brauchst die Prozedur eigentlich genau ein Mal. Da ich für Linux_ARM auf Github kein Binary bauen konnte, führst du die passende Datei einfach auf deinem PC oder Mac aus. Folge einfach den Schritten 1.-4. oben

      Löschen
  5. Hast du eine Idee, warum ich das hier kriege beim Anmelden:

    jq: error (at :1): date "6 Nov 2037 09:54:26 GMT" does not match format "%d %b %Y %H:%M:%S %Z"
    ERROR: cookie retrieval with refresh_token didn't work

    Den Token habe ich unter Windows Edge gemacht
    Atnr|EwICIBLkHdc0HX..... und den 1:1 in das Script kopiert zwischen ''
    Das Script läuft auf einem Homebridge Docker unter Synology

    Was meinst du dazu? Paddy

    AntwortenLöschen
    Antworten
    1. Hmmm, ich habe einen Workaround für das Jahr 2038 Problem auf 32-Bit Systemen gebaut. In deinem Datum steht 2037 - sollte also funktionieren.

      Was bringt denn:
      date -d "6 Nov 2037 09:54:26 GMT"
      oder
      date -d "@2141114066"
      oder
      date +"%d %b %Y %H:%M:%S %Z"
      (jeweils im Docker Container ausführen!)

      Löschen
    2. die bringen bei mir
      Fri Nov 6 10:54:26 CET 2037
      Fri Nov 6 10:54:26 CET 2037
      16 Nov 2021 17:44:44 CET
      hoffe es hilft dir & DANKE, dass du versuchst zu helfen!

      Löschen
    3. Bei mir läuft JQ v1.5.1, dieser coder funktioniert bei mir:

      echo '{"Expires":"6 Nov 2037 09:54:26 GMT"}' | jq -r '.Expires |= ( strptime("%d %b %Y %H:%M:%S %Z") | mktime ) | .Expires'

      Löschen
  6. Erst mal vielen Dank für das script. Ich bin immer wieder begeistert.
    Leider habe ich ein Problem mit der neuen Version:

    root@rpi3:/home/pi# cd alexa/
    root@rpi3:/home/pi/alexa# ./4alexa_remote_control.sh -a
    cookie expired, logging in again ...
    error: map_values is not defined
    .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 error: strptime is not defined
    .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 error: mktime is not defined
    .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 3 compile errors
    ERROR: cookie retrieval with refresh_token didn't work
    root@rpi3:/home/pi
    Bitte kontaktiere mich Alexander privat. Hab noch Ideen.

    AntwortenLöschen
    Antworten
    1. Neue installation. Hurra geht auf rpi5.
      Lösche vorherigen Beitrag. Ich WILL DIR was schenken Alexander. Schreib mir.
      dein wennes

      Löschen
  7. Hi, Thank you for all of your work! When using the alexa-cookie-cli-win-x64, the only option that returns an error is the --logger. What am I doing wrong?

    C:\Users\Tim\Downloads>alexa-cookie-cli-win-x64 -q -p amazon.com -a en-US -L en-US -P 8989 -l Alexa.txt
    Alexa.txt undefined
    C:\snapshot\alexa-cookie-cli\node_modules\alexa-cookie2\alexa-cookie.js:147
    _options.logger && _options.logger('Alexa-Cookie: Use as Login-Amazon-URL: ' + _options.amazonPage);
    ^

    TypeError: _options.logger is not a function
    at initConfig (C:\snapshot\alexa-cookie-cli\node_modules\alexa-cookie2\alexa-cookie.js:147:37)
    at AlexaCookie.generateAlexaCookie (C:\snapshot\alexa-cookie-cli\node_modules\alexa-cookie2\alexa-cookie.js:267:9)
    at Object. (C:\snapshot\alexa-cookie-cli\cli.js)
    at Module._compile (pkg/prelude/bootstrap.js:1751:22)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Function.runMain (pkg/prelude/bootstrap.js:1804:12)
    at internal/main/run_main_module.js:17:47


    Thank You In Adavance,

    Tim

    AntwortenLöschen
    Antworten
    1. Try without the log option. This will just log to StdOut.

      Löschen
  8. Hallo!

    Erstmal vielen, vielen Dank für das Script, funktioniert hervorragend und endlich kann meine Homematic auch Hinweise per Sprache ausgeben.

    Mir ist aber ein kleines Sicherheitsproblem aufgefallen: Nach dem ersten Login wird der Amazon-Cookie standardmäßig in /tmp abgelegt, und das world readable (bei Standard-umask).

    Das sollte entweder im Homedir sein, in einem Config-Folder, der nur vom User lesbar ist, oder wenn es schon /tmp sein muss dann sicherstellen, dass das chmod 0600 ist.

    Auf einem Single-User-System oder einem Home Automation Raspi ist das im Prinzip egal, aber es könnte ja mal jemand das in einem Multiuser-Environment auszuführen und dann hat er seinen Amazon-Account für alle anderen User geöffnet.

    AntwortenLöschen
    Antworten
    1. Deshalb kann man das TMP Verzeichnis auch per Config oder ENV Var auch woanders hin verlegen.
      Im ursprünglichen Artikel habe ich darauf verwiesen ;)

      Löschen
  9. Hallo, tolle Arbeit! Die Authentification bekomme ich hin, mit -a werden mir alle Devices angezeigt. Aber leider verstehe ich die Syntax nicht ganz, um eine Device in der Lautstärke zu regeln. Ich habe Devicenamen und seine Soll-Lautstätle in der .sh unter DEVICEVOLNAME und DEVICEVOLNORMAL angepasst, wenn ich das .sh aber aufrufe (./alexa_remote_control.sh) bekomme ich die Ausgabe "no alexa command recieved". Irgendwie auch klar, weil ich das Skript ohne Paramter aufgerufen habe. Bei der beschriebenen Syntax ist z.B. kein Parameter "Setvolume" o.ä. als Kommando dabei. Wie ist der Aufruf zu tätigen, um einen Alexa Dot z.B. auf einen bestimmten Lautstärkewert zu setzen? Danke und frohes Fest, Max

    AntwortenLöschen
    Antworten
    1. Ah, ich habe es gefunden. '-e vol:0-100' . Wichtig und hilfreich war es, den Hauptartikel bzw. die zugehörigen Kommentare zu lesen :-)

      Max

      Löschen
  10. Thanks a lot for jour job Alexander, (from Madrid)
    I was working with 2017 version, then I fixed the OTP by my own (we should join efforts, it tough me 1 week) Now I will try 2021 version as my routines stop working and now i need it to woork again with new Alexa api v2.

    Just two questions, reviwing the script: if i use freshtoken method, is it needed to have the SET_MFA_SECRET= ? is it needed the /oauth binary?

    AntwortenLöschen
    Antworten
    1. By using the Refresh_Token, no username/Password/MFA_SECRET is required anymore. That's the cool thing about it :)

      OATHTOOL is not required anymore either.

      Löschen
  11. Servus, ich habe ein kleines Problem mit dem Abspielen von TTS in die Standard Multiroom Gruppe Überall.

    Fehlermeldung: ERROR: unkown device dev:Überall
    In der .alexa.devicelist.txt steht komischerweise auch Ãberall drin. Vermutlich ein Problem mit der Codierung?

    Wenn ich die Gruppe mal auf Keller ändere, dann funktioniert der Aufruf bash alexa_remote_control.sh -d Keller -e speak:Hallo

    Mit
    bash alexa_remote_control.sh -d Überall -e speak:Hallo
    erhalte ich diese Fehlermeldung.

    Kannst du dir das evtl. mal anschauen?

    AntwortenLöschen
    Antworten
    1. Hast du die de_DE.UTF-8 Locale installiert? Es ist definitiv ein Problem mit der Kodierung.

      Bei mir heiß die Gruppe übrigens auch "Überall"

      Löschen
    2. Nein, anscheinend nicht. Befehl locale spuckt folgendes aus:
      loxberry@loxberry:~ $ locale
      LANG=en_GB.UTF-8
      LANGUAGE=
      LC_CTYPE="en_GB.UTF-8"
      LC_NUMERIC="en_GB.UTF-8"
      LC_TIME="en_GB.UTF-8"
      LC_COLLATE="en_GB.UTF-8"
      LC_MONETARY="en_GB.UTF-8"
      LC_MESSAGES="en_GB.UTF-8"
      LC_PAPER="en_GB.UTF-8"
      LC_NAME="en_GB.UTF-8"
      LC_ADDRESS="en_GB.UTF-8"
      LC_TELEPHONE="en_GB.UTF-8"
      LC_MEASUREMENT="en_GB.UTF-8"
      LC_IDENTIFICATION="en_GB.UTF-8"
      LC_ALL=

      Kann man das gefahrlos installieren?

      Löschen
  12. Hi,

    The script currently does not allow to control the volume of the music. (neither by -e vol: , nor by ENV SPEAKVOL ).

    Could you add this command? It would be very useful. I see that it is possible with this URL : https://alexa.amazon.fr/api/np/command?
    {"type":"VolumeLevelCommand","volumeLevel":20,"contentFocusClientId":null}

    AntwortenLöschen
    Antworten
    1. SPEAKVOL is only for setting the volume of SPEAK commands to a different level than the current playing volume.

      To change the volume of a device while it is playing a stream you should use:
      alexa_remote_control.sh -d DEVICE -e vol:20

      Löschen
  13. Suuuuper Arbeit!!!
    Funktionierte auf Anhieb.
    Werde das in FHEM einsetzen.
    Weiter so...

    AntwortenLöschen
  14. Hallo Alexander,
    erst mal vielen Dank für das grandiose Skript und die tollen Ideen dahinter. Eine Frage hätte ich noch:
    Ich gehe davon aus, dass alle SSML Sprachelemente, die zwischen "speak" und "/speak" stehen, an den Amazon Prozessor gesendet werden. Wenn dem so ist, müssten also Probleme, dass diverse SSML TAGs funktionieren und andere nicht, vermutlich an Amazon liegen. Ist das so?
    Beispielsweise funktionert das von dir gelistete "effect name="whispered" perfekt, aber ein " führt zur gleichen Meldung.

    Idee?

    Gruß
    Friedemann

    AntwortenLöschen
  15. Ah, hier werden nicht alle Zeichen demaskiert... 3. Versuch:
    Beispielsweise funktionert das von dir gelistete "effect name="whispered" perfekt, aber ein "emotion name=excited" liefert mir die gesprochene Fehlermeldung:"Ich habe momentan Schwierigkeiten auf deinen "Simonses Skills" zuzugreifen. Dabei ist "Simonses" als Lautsprache zu verstehen. Ich habe keinen blassen Schimmer, was Alexa mir da sagen will. Muss ich erst einen Skill dazu installieren? Welchen Sprachumfang der SSML kann ich denn verwenden? Für mich wäre es zum Beispiel sehr interessant, englische Texte sprechen zu lassen, aber die Verwendung des Tags führt zur gleichen Meldung.

    AntwortenLöschen
    Antworten
    1. "Simon says" ist ein eingebauter Skill, zu deutsch "sprich mir nach".
      Wenn die Ansage kommt, ist die SSML Syntax falsch oder evtl. wird ein nicht unterstütztes Feature verwendet.

      Löschen
  16. Hallo Alex,

    bei der aktuellen Version des Skriptes auf Github (v0.20e) funktioniert das Login mittels Refresh_Token nicht:

    cookie expired, logging in again ...
    date: invalid date '8 Sep 2042 13:37:00 GMT'
    date: invalid date '8 Sep 2042 13:37:00 GMT'
    date: invalid date '8 Sep 2042 13:37:00 GMT'
    date: invalid date '14 Sep 2022 13:37:00 GMT'
    date: invalid date '14 Sep 2022 13:37:00 GMT'
    trying to get CSRF from handlebars
    trying to get CSRF from devices-v2
    ERROR: no CSRF cookie received

    Mit v0.20d klappt das hingegen problemlos.

    Die letzte Änderung in v0.20e (removed call to jq's strptime function, replaced with bash function using 'date' to convert to epoch) scheint fehlerhaft zu arbeiten.

    AntwortenLöschen
  17. Ich habe dieses Shell Script erst jetzt duch Zufall entdeckt, erstmal ganz dickes Danke und Lob!

    2 Fragen:

    1. zu DefaultDevice "set device specific variables from JSON device list"
    Anstatt hier das erste Device zu wählen, wäre es sinnvoll das Default-Device per Variable im Script angeben zu können?

    2. Ist es möglich beim abspielen eines Sounds aus der Amazon soundbank/library die Lautstärke direkt mitzugeben?


    AntwortenLöschen
  18. Alexander, vielen Dank für den Script.
    Kann mir jemand mit dieser Fehler helfen?
    electrobot@srv-electrobot:/home/alexa$ sh alexa_remote_control.sh -a
    cookie expired, logging in again ...
    alexa_remote_control.sh: 595: /usr/bin/jq: not found
    alexa_remote_control.sh: 594: /usr/bin/jq: not found
    ERROR: cookie retrieval with refresh_token didn't work

    AntwortenLöschen
    Antworten
    1. Entweder ist "jq" bei dir in einem anderen Verzeichnis installiert (unwahrscheinlich aber möglich) oder Du hast es gar nicht installiert (höchstwahrscheinlich).

      Löschen
  19. Vielen Danke ! You saved my day !!

    AntwortenLöschen
  20. Wo und wie findet man eine Liste mit allen möglichen API's ?

    AntwortenLöschen
    Antworten
    1. Wie Alex die URLs für die Verwendung im Skript herausgefunden hat, kannst in seinem ersten Blog-Eintrag nachlesen: https://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html

      Löschen
  21. Thanks for the continued improvements, great job! I was curious, are multiturn commands supported using Text based approach, like send a command -> get a response and based on response -> send another command almost like interacting with an Alexa skill?

    AntwortenLöschen
    Antworten
    1. No, unfortunately that is not possible as there is no way to intercept the response.

      Löschen