[
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "\n# alexa-remote-control\ncontrol Amazon Alexa from command Line\n\nThe settings can now be controlled via environment variables.\n```\nBROWSER   - the User-Agent your browser sends in the request header\nAMAZON    - your Amazon domain\nALEXA     - the URL you would use for the Alexa Web App\nCURL      - location of your cURL binary\nOPTS      - any cURL options you require\nTMP       - location of the temp dir\nSPEAKVOL  - the volume for speak messages ( if set to 0, volume levels are left untouched)\nNORMALVOL - if no current playing volume can be determined, fall back to normal volume\nVOLMAXAGE - max. age in minutes before volume is re-read from API\nDEVICEVOLNAME   - a list of device names with specific volume settings (space separated)\nDEVICEVOLSPEAK  - a list of speak volume levels - matching the devices above\nDEVICEVOLNORMAL - a list of normal volume levels- matching the devices above\n                  (current playing volume takes precedence for normal volume)\nREFRESH_TOKEN   - the new preference over EMAIL/PASSWORD can be obtained here: https://github.com/adn77/alexa-cookie-cli\n```\n\n```\nalexa-remote-control [-d <device>|ALL] -e <pause|play|next|prev|fwd|rwd|shuffle|repeat|vol:<0-100>> |\n                    -b [list|<\"AA:BB:CC:DD:EE:FF\">] | -q | -n | -r <\"station name\"|stationid> |\n                    -s <trackID|'Artist' 'Album'> | -t <ASIN> | -u <seedID> | -v <queueID> |\n                    -w <playlistId> | -i | -p | -P | -S | -a | -z | -l | -h |\n                    -m <multiroom_device> [device_1 .. device_X] | -lastalexa | -lastcommand\n\n   -e : run command, additional SEQUENCECMDs:\n        weather,traffic,flashbriefing,goodmorning,singasong,tellstory,\n        speak:'<text/ssml>',automation:'<routine name>',sound:<soundeffect_name>,\n        textcommand:'<anything you would otherwise say to Alexa>',\n        playmusic:<channel e.g. TUNEIN, AMAZON_MUSIC>:'<music name>'\n\n   -b : connect/disconnect/list bluetooth device\n   -c : list 'playmusic' channels\n   -q : query queue\n   -n : query notifications\n   -r : play tunein radio\n   -s : play library track/library album\n   -t : play Prime playlist\n   -u : play Prime station\n   -v : play Prime historical queue\n   -w : play library playlist\n   -i : list imported library tracks\n   -p : list purchased library tracks\n   -P : list Prime playlists\n   -S : list Prime stations\n   -a : list available devices\n   -m : delete multiroom and/or create new multiroom containing devices\n   -lastalexa : print device that received the last voice command\n   -lastcommand : print last voice command or last voice command of specific device\n   -login     : Logs in, without further command (downloads cookie)\n   -z : print current volume level\n   -l : logoff\n   -h : help\n```\n\nLogin via REFRESH_TOKEN\n----\nThe 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\n\nhttps://blog.loetzimmer.de/2021/09/alexa-remote-control-shell-script.html\n"
  },
  {
    "path": "alexa_remote_control.sh",
    "content": "#!/bin/sh\n#\n# Amazon Alexa Remote Control\n#  alex(at)loetzimmer.de\n#\n# 2017-10-10: v0.1 initial release\n# 2017-10-11: v0.2 TuneIn Station Search\n# 2017-10-11: v0.2a commands on special device \"ALL\" are executed on all ECHO+WHA\n# 2017-10-16: v0.3 added playback of library tracks\n# 2017-10-24: v0.4 added playback information\n# 2017-11-21: v0.5 added Prime station and playlist\n# 2017-11-22: v0.6 added Prime historical queue and replaced getopts\n# 2017-11-25: v0.6a cURL is now configurable\n# 2017-11-25: v0.7 added multiroom create/delete, playback of library playlist\n# 2017-11-30: v0.7a added US config, fixed device names containing spaces\n# 2017-12-07: v0.7b added Bluetooth connect/disconnect\n# 2017-12-18: v0.7c fixed US version\n# 2017-12-19: v0.7d fixed AWK csrf extraction on some systems\n# 2017-12-20: v0.7e moved get_devlist after check_status\n# 2018-01-08: v0.7f added echo-show to ALL group, TuneIn station can now be up to 6 digits\n# 2018-01-08: v0.8 added bluetooth list function\n# 2018-01-10: v0.8a abort when login was unsuccessful\n# 2018-01-25: v0.8b added echo-spot to ALL group\n# 2018-01-28: v0.8c added configurable browser string\n# 2018-02-17: v0.8d no need to write the cookie file on every \"check_status\"\n# 2018-02-27: v0.8e added \"lastalexa\" option for HA-Bridge to send its command to a specific device\n#               (Markus Wennesheimer: https://wennez.wordpress.com/light-on-with-alexa-for-each-room/)\n# 2018-02-27: v0.9 unsuccessful logins will now give a short info how to debug the login\n# 2018-03-09: v0.9a workaround for login problem, force curl to use http1.1\n# 2018-05-17: v0.9b update browser string and accept language\n# 2018-05-23: v0.9c update accept language (again)\n# 2018-06-12: v0.10 introducing TTS and more\n#               (thanks to Michael Geramb and his openHAB2 Amazon Echo Control binding)\n#               https://github.com/openhab/openhab2-addons/tree/master/addons/binding/org.openhab.binding.amazonechocontrol\n#               (thanks to Ralf Otto for implementing this feature in this script)\n# 2018-06-13: v0.10a added album play of imported library\n# 2018-06-18: v0.10b added Alexa routine execution\n# 2019-01-22: v0.11 added repeat command, added environment variable parsing\n# 2019-02-03: v0.11a fixed string escape for automation and speak commands\n# 2019-02-10: v0.12 added \"-d ALL\" to the plain version, lastalexa now checks for SUCCESS activityStatus\n# 2019-02-14: v0.12a reduced the number of replaced characters for TTS and automation\n# 2019-06-18: v0.12b fixed CSRF\n# 2019-06-28: v0.12c properly fixed CSRF\n# 2019-07-08: v0.13 added support for Multi-Factor Authentication\n#               (thanks to rich-gepp https://github.com/rich-gepp)\n# 2019-08-05: v0.14 added Volume setting via routine, and $SPEAKVOL\n# 2019-11-18: v0.14a download 200 routines instead of only the first 20\n# 2019-12-23: v0.14b Trigger routines by either utterance or routine name\n# 2019-12-30: v0.15 re-worked the volume setting for TTS commands\n# 2020-01-03: v0.15a introduce some proper \"get_volume\"\n# 2020-01-08: v0.15b cleaned merge errors\n# 2020-02-03: v0.15c SPEAKVOL of 0 leaves the volume setting untouched\n# 2020-02-09: v0.16 TTS to Multiroom groups via USE_ANNOUNCEMENT_FOR_SPEAK + SSML for TTS\n#               (!!! requires Announcement feature to be enabled in each device !!!)\n# 2020-02-09: v0.16a added sound library - only very few sounds are actually supported\n#               ( https://developer.amazon.com/en-US/docs/alexa/custom-skills/ask-soundlibrary.html )\n# 2020-06-15: v0.16b added \"lastcommand\" option\n#               (thanks to Trinitus01 https://github.com/trinitus01)\n# 2020-07-07: v0.16c fixed NORMALVOL if USE_ANNOUNCEMENT_FOR_SPEAK is set\n# 2020-12-12: v0.17 added textcommand which lets you send anything via CLI you would otherwise say to Alexa\n#               ( https://github.com/thorsten-gehrig/alexa-remote-control/issues/108 )\n# 2020-12-12: v0.17a sounds now benefit from SPEAKVOL\n#                    fixed TuneIn IDs to also play podcasts\n# 2021-01-28: v0.17b fixed new API endpoint for automations\n#               (thanks to Michael Winkler)\n# 2021-01-28: v0.17c simplified volume detection using new DeviceVolumes endpoint\n#               (thanks to Ingo Fischer)\n# 2021-05-27: v0.18 complete rework of sequence commands especially for TTS\n#                    Announcement feature is no longer required due to inconsistent SSML handling\n# 2021-09-02: v0.19 Playing TuneIn works again using new entertainment API endpoint\n#               Added playmusic (Alexa.Music.PlaySearchPhrase) as command, for available channels use \"-c\"\n#               Note: playmusic is not multi-room capable, doing so might lead to unexpected results\n# 2021-09-13: v0.20 implemented device registration refresh_token cookie exchange flow as an alternative\n#               to logging in\n# 2021-09-15: v0.20a optimized speak commands to use less JQ. This is useful in low-resource environments\n# 2021-10-07: v0.20b fixed different cookie naming for amazon.com\n# 2021-11-16: v0.20c fixed AlexaApp device selection: since they're all called \"This Device\" use corresponding\n#               line in /tmp/.alexa.devicelist.txt, e.g.: -d \"This Device=A2TF17PFR55MTB=ce0123456789abcdef01=VOX\"\n#               -lastalexa now returns this string. Make sure to put the device in double quotes!\n# 2022-02-04: v0.20d minor volume fix (write volume to volume cache when volume is changed)\n# 2022-06-29: v0.20e removed call to jq's strptime function, replaced with bash function using 'date' to convert to epoch\n# 2024-01-29: v0.21 removed legacy login methods as they were no longer working\n#                   implemented new API calls for -lastalexa and -lastcommand\n#                   there is now an OS-type switch that hopefully handles OSX and BSD date creation\n# 2024-01-31: v0.21a trying all different date options which come to mind (first working wins)\n# 2024-02-01: v0.21b changed the output of -lastalexa back to the output of devicelist.txt\n# 2024-04-06: v0.22 changed the date calculation once again, now the date processing ignores the actual cookie validity\n#                    and simply sets it to \"now + COOKIE_LIFETIME\"\n# 2025-11-07: v0.23 /api/bootstrap is gone, switched to /api/customer-status \n#\t\t\t\t(thanks once again to Ingo Fischer)\n#\n###\n#\n# (no BASHisms were used, should run with any shell)\n# - requires cURL for web communication\n# - (GNU) sed and awk for extraction\n# - jq as command line JSON parser (optional for the fancy bits)\n# - base64 for B64 encoding (make sure \"-w 0\" option is available on your platform)\n#\n##########################################\n\n# this can be obtained by doing the device registration login flow\n#  e.g. from here: https://github.com/adn77/alexa-cookie-cli/\nSET_REFRESH_TOKEN=''\n\nSET_TTS_LOCALE='de-DE'\n\nSET_AMAZON='amazon.de'\n#SET_AMAZON='amazon.com'\n\nSET_ALEXA='alexa.amazon.de'\n#SET_ALEXA='pitangui.amazon.com'\n\n# cURL binary\nSET_CURL='/usr/bin/curl'\n\n# cURL options\n#  -k : if your cURL cannot verify CA certificates, you'll have to trust any\n#  --compressed : if your cURL was compiled with libz you may use compression\n#  --http1.1 : cURL defaults to HTTP/2 on HTTPS connections if available\nSET_OPTS='--compressed --http1.1'\n#SET_OPTS='-k --compressed --http1.1'\n\n# browser identity\nSET_BROWSER='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:1.0) bash-script/1.0'\n#SET_BROWSER='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0'\n\n# jq binary\nSET_JQ='/usr/bin/jq'\n\n# tmp path\nSET_TMP=\"/tmp\"\n\n# Volume for speak commands (a SPEAKVOL of 0 leaves the volume settings untouched)\nSET_SPEAKVOL=\"0\"\n# if no current playing volume can be determined, fall back to normal volume\nSET_NORMALVOL=\"10\"\n\n# Device specific volumes (overriding the above)\n# SET_DEVICEVOLNAME=\"EchoDot2ndGen Echo1stGen\"\n# SET_DEVICEVOLSPEAK=\"100 30\"\n# SET_DEVICEVOLNORMAL=\"100 20\"\nSET_DEVICEVOLNAME=\"\"\nSET_DEVICEVOLSPEAK=\"\"\nSET_DEVICEVOLNORMAL=\"\"\n\n# max. age in minutes before volume is read from API (local cache time)\nSET_VOLMAXAGE=\"1\"\n\n###########################################\n# nothing to configure below here\n#\n\n# retrieving environment variables if any are set\nREFRESH_TOKEN=${REFRESH_TOKEN:-$SET_REFRESH_TOKEN}\nAMAZON=${AMAZON:-$SET_AMAZON}\nALEXA=${ALEXA:-$SET_ALEXA}\nBROWSER=${BROWSER:-$SET_BROWSER}\nCURL=${CURL:-$SET_CURL}\nOPTS=${OPTS:-$SET_OPTS}\nTTS_LOCALE=${TTS_LOCALE:-$SET_TTS_LOCALE}\nTMP=${TMP:-$SET_TMP}\nJQ=${JQ:-$SET_JQ}\nSPEAKVOL=${SPEAKVOL:-$SET_SPEAKVOL}\nNORMALVOL=${NORMALVOL:-$SET_NORMALVOL}\nVOLMAXAGE=${VOLMAXAGE:-$SET_VOLMAXAGE}\nDEVICEVOLNAME=${DEVICEVOLNAME:-$SET_DEVICEVOLNAME}\nDEVICEVOLSPEAK=${DEVICEVOLSPEAK:-$SET_DEVICEVOLSPEAK}\nDEVICEVOLNORMAL=${DEVICEVOLNORMAL:-$SET_DEVICEVOLNORMAL}\n\nCOOKIE=\"${TMP}/.alexa.cookie\"\nDEVLIST=\"${TMP}/.alexa.devicelist\"\nCOOKIE_LIFETIME=$(( 24 * 60 * 60 )) # default lifetime of one day before revalidation\n\nLIST=\"\"\nLOGOFF=\"\"\nCOMMAND=\"\"\nTTS=\"\"\nUTTERANCE=\"\"\nSEQUENCECMD=\"\"\nSEQUENCEVAL=\"\"\nSEARCHPHRASE=\"\"\nPROVIDERID=\"\"\nSTATIONID=\"\"\nCHANNEL=\"\"\nQUEUE=\"\"\nSONG=\"\"\nALBUM=\"\"\nARTIST=\"\"\nTYPE=\"\"\nASIN=\"\"\nSEEDID=\"\"\nHIST=\"\"\nLEMUR=\"\"\nCHILD=\"\"\nPLIST=\"\"\nBLUETOOTH=\"\"\nLASTALEXA=\"\"\nLASTCOMMAND=\"\"\nGETVOL=\"\"\nNOTIFICATIONS=\"\"\n\nusage()\n{\n\techo \"$0 [-d <device>|ALL] -e <pause|play|next|prev|fwd|rwd|shuffle|repeat|vol:<0-100>> |\"\n\techo \"          -b [list|<\\\"AA:BB:CC:DD:EE:FF\\\">] | -q | -n | -r <\\\"station name\\\"|stationId> |\"\n\techo \"          -s <trackID|'Artist' 'Album'> | -t <ASIN> | -u <seedID> | -v <queueID> | -w <playlistId> |\"\n\techo \"          -i | -p | -P | -S | -a | -m <multiroom_device> [device_1 .. device_X] | -lastalexa | -lastcommand | -z | -l | -h\"\n\techo\n\techo \"   -e : run command, additional SEQUENCECMDs:\"\n\techo \"        weather,traffic,flashbriefing,goodmorning,singasong,tellstory,\"\n\techo \"        speak:'<text/ssml>',automation:'<routine name>',sound:<soundeffect_name>,\"\n\techo \"        textcommand:'<anything you would otherwise say to Alexa>',\"\n\techo \"        playmusic:<channel e.g. TUNEIN, AMAZON_MUSIC>:'<music name>'\"\n\techo \"   -b : connect/disconnect/list bluetooth device\"\n\techo \"   -c : list 'playmusic' channels\"\n\techo \"   -q : query queue\"\n\techo \"   -n : query notifications\"\n\techo \"   -r : play tunein radio\"\n\techo \"   -s : play library track/library album\"\n\techo \"   -t : play Prime playlist\"\n\techo \"   -u : play Prime station\"\n\techo \"   -v : play Prime historical queue\"\n\techo \"   -w : play library playlist\"\n\techo \"   -i : list imported library tracks\"\n\techo \"   -p : list purchased library tracks\"\n\techo \"   -P : list Prime playlists\"\n\techo \"   -S : list Prime stations\"\n\techo \"   -a : list available devices\"\n\techo \"   -m : delete multiroom and/or create new multiroom containing devices\"\n\techo \"   -lastalexa : print device that received the last voice command\"\n\techo \"   -lastcommand : print last voice command or last voice command of specific device\"\n\techo \"   -z : print current volume level\"\n\techo \"   -login : Logs in, without further command\"\n\techo \"   -l : logoff\"\n\techo \"   -h : help\"\n}\n\nwhile [ \"$#\" -gt 0 ] ; do\n\tcase \"$1\" in\n\t\t--version)\n\t\t\techo \"v0.23\"\n\t\t\texit 0\n\t\t\t;;\n\t\t-d)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tDEVICE=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-e)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tCOMMAND=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-b)\n\t\t\tif [ \"${2#-}\" = \"${2}\" -a -n \"$2\" ] ; then\n\t\t\t\tBLUETOOTH=$2\n\t\t\t\tshift\n\t\t\telse\n\t\t\t\tBLUETOOTH=\"null\"\n\t\t\tfi\n\t\t\t;;\n\t\t-m)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tLEMUR=$2\n\t\t\tshift\n\t\t\twhile [ \"${2#-}\" = \"${2}\" -a -n \"$2\" ] ; do\n\t\t\t\tCHILD=\"${CHILD} ${2}\"\n\t\t\t\tshift\n\t\t\tdone\n\t\t\t;;\n\t\t-r)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tSTATIONID=$2\n\t\t\tshift\n\t\t\t# stationIDs are \"s1234\" or \"s12345\" \n\t\t\tif [ -n \"${STATIONID##s[0-9][0-9][0-9][0-9]*}\" -a -n \"${STATIONID##p[0-9][0-9][0-9][0-9]*}\" ] ; then\n\t\t\t\t# search for station name\n\t\t\t\tSTATIONID=$(${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')\n\t\t\t\tif [ -z \"$STATIONID\" ] ; then\n\t\t\t\t\techo \"ERROR: no Station \\\"$2\\\" found on TuneIn\"\n\t\t\t\t\texit 1\n\t\t\t\tfi\n\t\t\tfi\n\t\t\t;;\n\t\t-s)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tSONG=$2\n\t\t\tshift\n\t\t\tif [ \"${2#-}\" = \"${2}\" -a -n \"$2\" ] ; then\n\t\t\t\tALBUM=$2\n\t\t\t\tARTIST=$SONG\n\t\t\t\tshift\n\t\t\tfi\n\t\t\t;;\n\t\t-t)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tASIN=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-u)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tSEEDID=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-v)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tHIST=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-w)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tPLIST=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-login)\n\t\t\tLOGIN=\"true\"\n\t\t\t;;\n\t\t-l)\n\t\t\tLOGOFF=\"true\"\n\t\t\t;;\n\t\t-a)\n\t\t\tLIST=\"true\"\n\t\t\t;;\n\t    -c)\n\t\t\tCHANNEL=\"true\"\n\t\t\t;;\n\t\t-i)\n\t\t\tTYPE=\"IMPORTED\"\n\t\t\t;;\n\t\t-p)\n\t\t\tTYPE=\"PURCHASES\"\n\t\t\t;;\n\t\t-P)\n\t\t\tPRIME=\"prime-playlist-browse-nodes\"\n\t\t\t;;\n\t\t-S)\n\t\t\tPRIME=\"prime-sections\"\n\t\t\t;;\n\t\t-q)\n\t\t\tQUEUE=\"true\"\n\t\t\t;;\n\t\t-n)\n\t\t\tNOTIFICATIONS=\"true\"\n\t\t\t;;\n\t\t-lastalexa)\n\t\t\tLASTALEXA=\"true\"\n\t\t\t;;\n\t\t-lastcommand)\n\t\t\tLASTCOMMAND=\"true\"\n\t\t\t;;\n\t\t-z)\n\t\t\tGETVOL=\"true\"\n\t\t\t;;\n\t\t-h|-\\?|--help)\n\t\t\tusage\n\t\t\texit 0\n\t\t\t;;\n\t\t*)\n\t\t\techo \"ERROR: unknown option ${1}\"\n\t\t\tusage\n\t\t\texit 1\n\t\t\t;;\n\tesac\n\tshift\ndone\n\ncase \"$COMMAND\" in\n\tpause)\n\t\t\tCOMMAND='{\"type\":\"PauseCommand\"}'\n\t\t\t;;\n\tplay)\n\t\t\tCOMMAND='{\"type\":\"PlayCommand\"}'\n\t\t\t;;\n\tnext)\n\t\t\tCOMMAND='{\"type\":\"NextCommand\"}'\n\t\t\t;;\n\tprev)\n\t\t\tCOMMAND='{\"type\":\"PreviousCommand\"}'\n\t\t\t;;\n\tfwd)\n\t\t\tCOMMAND='{\"type\":\"ForwardCommand\"}'\n\t\t\t;;\n\trwd)\n\t\t\tCOMMAND='{\"type\":\"RewindCommand\"}'\n\t\t\t;;\n\tshuffle)\n\t\t\tCOMMAND='{\"type\":\"ShuffleCommand\",\"shuffle\":\"true\"}'\n\t\t\t;;\n\trepeat)\n\t\t\tCOMMAND='{\"type\":\"RepeatCommand\",\"repeat\":true}'\n\t\t\t;;\n\tvol:*)\n\t\t\tVOL=${COMMAND##*:}\n\t\t\t# volume as integer!\n\t\t\tif [ $VOL -le 100 -a $VOL -ge 0 ] ; then\n\t\t\t\tSEQUENCECMD='Alexa.DeviceControls.Volume'\n\t\t\t\tSEQUENCEVAL=',\\\"value\\\":\\\"'${VOL}'\\\"'\n\t\t\telse\n\t\t\t\techo \"ERROR: volume should be an integer between 0 and 100\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\t;;\n\ttextcommand:*)\n\t\t\tSEQUENCECMD='Alexa.TextCommand\\\",\\\"skillId\\\":\\\"amzn1.ask.1p.tellalexa'\n\t\t\tSEQUENCEVAL=$(echo ${COMMAND##textcommand:} | sed s/\\\"/\\'/g)\n\t\t\tSEQUENCEVAL=',\\\"text\\\":\\\"'${SEQUENCEVAL}'\\\"'\n\t\t\t;;\n\tspeak:*)\n\t\t\tTTS=$(echo ${COMMAND##speak:} | sed s/\\\"/\\'/g)\n\t\t\tTTS=',\\\"textToSpeak\\\":\\\"'${TTS}'\\\"'\n\t\t\tSEQUENCECMD='Alexa.Speak'\n\t\t\tSEQUENCEVAL=$TTS\n\t\t\t;;\n\tsound:*)\n\t\t\tSEQUENCECMD='Alexa.Sound'\n\t\t\tSEQUENCEVAL=',\\\"soundStringId\\\":\\\"'${COMMAND##sound:}'\\\"'\n\t\t\t;;\n\tautomation:*)\n\t\t\tSEQUENCECMD='automation'\n\t\t\tUTTERANCE=$(echo ${COMMAND##automation:} | sed -r 's/[\"\\\\]/ /g')\n\t\t\t;;\n\tweather)\n\t\t\tSEQUENCECMD='Alexa.Weather.Play'\n\t\t\t;;\n\ttraffic)\n\t\t\tSEQUENCECMD='Alexa.Traffic.Play'\n\t\t\t;;\n\tflashbriefing)\n\t\t\tSEQUENCECMD='Alexa.FlashBriefing.Play'\n\t\t\t;;\n\tgoodmorning)\n\t\t\tSEQUENCECMD='Alexa.GoodMorning.Play'\n\t\t\t;;\n\tsingasong)\n\t\t\tSEQUENCECMD='Alexa.SingASong.Play'\n\t\t\t;;\n\ttellstory)\n\t\t\tSEQUENCECMD='Alexa.TellStory.Play'\n\t\t\t;;\n\tplaymusic:*)\n\t\t\tSEQUENCECMD='Alexa.Music.PlaySearchPhrase'\n\t\t\tPROVIDERID=${COMMAND#*:}\n\t\t\tPROVIDERID=${PROVIDERID%:*}\n\t\t\tSEQUENCEVAL=',\\\"musicProviderId\\\":\\\"'${PROVIDERID}'\\\",'\n\t\t\tSEARCHPHRASE=$(echo ${COMMAND##*:} | sed s/\\\"/\\'/g)\n\t\t\t;;\n\t\"\")\n\t\t\t;;\n\t*)\n\t\t\techo \"ERROR: unknown command \\\"${COMMAND}\\\"!\"\n\t\t\tusage\n\t\t\texit 1\n\t\t\t;;\nesac\n\n#\n# Amazon Login\n#\nlog_in()\n{\nrm -f ${DEVLIST}.json\nrm -f ${COOKIE}\nrm -f ${TMP}/.alexa.*.list\n\nif [ -z \"${REFRESH_TOKEN}\" ] ; then\n\techo \"Sorry, the very thing this project started with, namely the reverse engineered\"\n\techo \" login to the Amazon web page does no longer work. The Alexa login page has\"\n\techo \" been shut down in favor of a much more modern login process.\"\n\techo\n\techo \"Please use the device login process https://github.com/adn77/alexa-cookie-cli\"\n\techo \" all you need is the 'refreshToken' looking sth. like 'Atnr|...'\"\n\n\texit 1\nelse\n\tnow=$(date +%s)\n\texp=$(( now + COOKIE_LIFETIME ))\n\n\t# the date processing ignores the actual cookie validity and simply sets it to \"now + COOKIE_LIFETIME\"\n\t${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 |\\\n\t ${JQ} -r --arg exp $exp '.response.tokens.cookies | to_entries[] | .key as $domain | .value[] | map_values(if . == true then \"TRUE\" elif . == false then \"FALSE\" else . end) | .Expires |= $exp | [(if .HttpOnly==\"TRUE\" then (\"#HttpOnly_\" + $domain) else $domain end), \"TRUE\", .Path, .Secure, .Expires, .Name, .Value] | @tsv' > ${COOKIE}\n\n\tif [ -z \"$(grep \"\\.${AMAZON}.*\\sat-\" ${COOKIE})\" ] ; then\n\t\techo \"ERROR: cookie retrieval with refresh_token didn't work\"\n\t\texit 1\n\tfi\nfi\n\n#\n# get CSRF\n#\n${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n https://${ALEXA}/api/language > /dev/null\n\nif [ -z \"$(grep \"\\.${AMAZON}.*\\scsrf\" ${COOKIE})\" ] ; then\n\techo \"trying to get CSRF from handlebars\"\n\t${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t https://${ALEXA}/templates/oobe/d-device-pick.handlebars > /dev/null\nfi\n\nif [ -z \"$(grep \"\\.${AMAZON}.*\\scsrf\" ${COOKIE})\" ] ; then\n\techo \"trying to get CSRF from devices-v2\"\n\t${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t https://${ALEXA}/api/devices-v2/device?cached=false > /dev/null\nfi\n\nif [ -z \"$(grep \"\\.${AMAZON}.*\\scsrf\" ${COOKIE})\" ] ; then\n\techo \"ERROR: no CSRF cookie received\"\n\texit 1\nfi\n}\n\n#\n# get JSON device list\n#\nget_devlist()\n{\n\t${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\"\\\n\t \"https://${ALEXA}/api/devices-v2/device?cached=false\" > ${DEVLIST}.json\n\n\t${JQ} -r '.devices[] | \"\\(.accountName)=\\(.deviceType)=\\(.serialNumber)=\\(.deviceFamily)\"' ${DEVLIST}.json > ${DEVLIST}.txt\n\t${JQ} -r '.devices[] | select( .appDeviceList | length >0 ) as $p | .appDeviceList[] | \"\\($p.accountName)=\\(.deviceType)=\\(.serialNumber)=\\($p.deviceFamily)\"' ${DEVLIST}.json >> ${DEVLIST}.txt\n\t${JQ} -r '.devices[] | select(.deviceFamily == \"WHA\") | \"\\(.accountName)=\\(.clusterMembers[])\"' ${DEVLIST}.json > ${DEVLIST}_wha.txt\n}\n\ncheck_status()\n{\n#\n#  returns the current authentication state (HTTP/200)\n#\n\tAUTHSTATUS=$(${CURL} ${OPTS} -s -w \"%{http_code}\" -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L https://${ALEXA}/api/customer-status -o ${TMP}/.alexa.authstatus.json)\n\n\tcase $AUTHSTATUS in\n\t\t200)\n\t\t\tMEDIAOWNERCUSTOMERID=$(${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L https://${ALEXA}/api/users/me | ${JQ} -r '.id')\n\t\t\treturn 1\n\t\t\t;;\n\t\t401|403)\n\t\t\treturn 0\n\t\t\t;;\n\t\t*)\n\t\t\t;;\n\tesac\n\techo \"ERROR: /api/customer-status returned unexpected HTTP/${AUTHSTATUS}\"\n}\n\n#\n# set device specific variables from JSON device list\n#\nset_var()\n{\n\tDEVICE=$(echo ${DEVICE} | sed -r 's/%20/ /g')\n\n\tif [ -z \"${DEVICE}\" ] ; then\n\t\t# if no device was supplied, use the first Echo(dot) in device list\n\t\techo -n \"setting default device to: \"\n\t\tDEVICE=$(grep -m 1 -E \"ECHO|KNIGHT|ROOK\" ${DEVLIST}.txt | cut -d'=' -f1)\n\t\techo ${DEVICE}\n\tfi\n\n\tDEVICESERIALNUMBER=$(grep -m 1 \"${DEVICE}\" ${DEVLIST}.txt)\n\tDEVICESERIALNUMBER=${DEVICESERIALNUMBER#*=}\n\n\tDEVICEFAMILY=${DEVICESERIALNUMBER##*=}\n\tDEVICETYPE=${DEVICESERIALNUMBER%%=*}\n\tDEVICESERIALNUMBER=${DEVICESERIALNUMBER#*=}\n\tDEVICESERIALNUMBER=${DEVICESERIALNUMBER%=*}\n\n\t# customerId is now retrieved from the logged in user\n\t# the customerId in the device list is always from the user registering the device initially\n\t# MEDIAOWNERCUSTOMERID=$(${JQ} --arg device \"${DEVICE}\" -r '.devices[] | select(.accountName == $device) | .deviceOwnerCustomerId' ${DEVLIST}.json)\n\n\tif [ -z \"${DEVICESERIALNUMBER}\" ] ; then\n\t\techo \"ERROR: unkown device dev:${DEVICE}\"\n\t\texit 1\n\tfi\n}\n\n#\n# list available devices from JSON device list\n#\nlist_devices()\n{\n\t${JQ} -r '.devices[].accountName' ${DEVLIST}.json\n}\n\n#\n# sanitize search phrase\n#  ARG1 - sequence command (e.g. Alexa.Music.PlaySearchPhrase)\n#  ARG2 - musicProviderID ( TUNEIN, AMASON_MUSIC, CLOUDPLAYER, SPOTIFY, APPLE_MUSIC, DEEZER, I_HEART_RADIO )\n#  ARG3 - search phrase\n#\nsanitize_search()\n{\n\tif [ -n \"$1\" -a -n \"$2\" -a -n \"$3\" ] ; then\n\t\tJSON='{\"type\":\"'${1}'\",\"operationPayload\":\"{\\\"locale\\\":\\\"'${TTS_LOCALE}'\\\",\\\"musicProviderId\\\":\\\"'${2}'\\\",\\\"searchPhrase\\\":\\\"'${3}'\\\"}\"}'\n\telse\n\t\tJSON='{\"type\":\"'${SEQUENCECMD}'\",\"operationPayload\":\"{\\\"locale\\\":\\\"'${TTS_LOCALE}'\\\",\\\"musicProviderId\\\":\\\"'${PROVIDERID}'\\\",\\\"searchPhrase\\\":\\\"'${SEARCHPHRASE}'\\\"}\"}'\n\tfi\n\n\t${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"${JSON}\" \\\n\t \"https://${ALEXA}/api/behaviors/operation/validate\" | ${JQ} -r '.operationPayload.sanitizedSearchPhrase'\n}\n\n#\n# build node_to_execute string\n#  ARG1 - SEQUENCECMD\n#  ARG2 - SEQUENCEVAL\n#\nnode()\n{\n\tif [ -n \"$1\" -a -n \"$2\" ] ; then\n\t\techo '{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\\\"type\\\":\\\"'${1}'\\\",\\\"operationPayload\\\":{\\\"deviceType\\\":\\\"'${DEVICETYPE}'\\\",\\\"deviceSerialNumber\\\":\\\"'${DEVICESERIALNUMBER}'\\\",\\\"customerId\\\":\\\"'${MEDIAOWNERCUSTOMERID}'\\\",\\\"locale\\\":\\\"'${TTS_LOCALE}'\\\"'${2}'}}'\n\telse\n\t\techo '{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\\\"type\\\":\\\"'${SEQUENCECMD}'\\\",\\\"operationPayload\\\":{\\\"deviceType\\\":\\\"'${DEVICETYPE}'\\\",\\\"deviceSerialNumber\\\":\\\"'${DEVICESERIALNUMBER}'\\\",\\\"customerId\\\":\\\"'${MEDIAOWNERCUSTOMERID}'\\\",\\\"locale\\\":\\\"'${TTS_LOCALE}'\\\"'${SEQUENCEVAL}'}}'\n\tfi\n}\n\n#\n# create comma separated string\n#\nadd_node()\n{\n\tif [ -n \"$1\" ] ; then\n\t\tif [ -n \"$2\" ] ; then\n\t\t\techo ${1}','${2}\n\t\telse\n\t\t\techo ${1}\n\t\tfi\n\tfi\n}\n\n#\n# execute command\n#\nrun_cmd()\n{\nif [ -n \"${SEQUENCECMD}\" ] ; then\n\tif [ \"${SEQUENCECMD}\" = 'automation' ] ; then\n\n\t\t${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t\t -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t\t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n\t\t \"https://${ALEXA}/api/behaviors/v2/automations?limit=200\" > \"${TMP}/.alexa.automation\"\n\n\t\tAUTOMATION=$(${JQ} --arg utterance \"${UTTERANCE}\" -r '.[] | select( .triggers[].payload.utterance == $utterance) | .automationId' \"${TMP}/.alexa.automation\")\n\t\tif [ -z \"${AUTOMATION}\" ] ; then\n\t\t\tAUTOMATION=$(${JQ} --arg utterance \"${UTTERANCE}\" -r '.[] | select( .name == $utterance) | .automationId' \"${TMP}/.alexa.automation\")\n\t\t\tif [ -z \"${AUTOMATION}\" ] ; then\n\t\t\t\techo \"ERROR: no such utterance '${UTTERANCE}' in Alexa routines\"\n\t\t\t\trm -f \"${TMP}/.alexa.automation\"\n\t\t\t\texit 1\n\t\t\tfi\n\t\tfi\n\t\tSEQUENCE=$(${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\")\n\t\trm -f \"${TMP}/.alexa.automation\"\n\n\t\tALEXACMD='{\"behaviorId\":\"'${AUTOMATION}'\",\"sequenceJson\":\"'${SEQUENCE}'\",\"status\":\"ENABLED\"}'\n\telse\n\t\tVOLUMEPRENODESTOEXECUTE=''\n\t\tVOLUMEPOSTNODESTOEXECUTE=''\n\t\tNODESTOEXECUTE=''\n\n\t\t# sanitize search phrase\n\t\tif [ -n \"${SEARCHPHRASE}\" -a -n \"${PROVIDERID}\" ] ; then\n\t\t\tSEQUENCEVAL=${SEQUENCEVAL}'\\\"searchPhrase\\\":\\\"'${SEARCHPHRASE}'\\\",\\\"sanitizedSearchPhrase\\\":\\\"'$(sanitize_search)'\\\"'\n\t\tfi\n\n\t\t# iterate over member devices if target is multiroom\n\t\t# !!! this is no true multi-room - it just tries to play on every member device in parallel !!!\n\t\tif [ \"${DEVICEFAMILY}\" = \"WHA\" ] ; then\n\t\t\tMEMBERDEVICESERIALS=$(grep \"${DEVICE}\" ${DEVLIST}_wha.txt | cut -d'=' -f 2)\n\t\t\tfor DEVICESERIALNUMBER in $MEMBERDEVICESERIALS ; do\n\t\t\t\tDEVICETYPE=$(grep \"${DEVICESERIALNUMBER}\" ${DEVLIST}.txt | cut -d'=' -f 2)\n\t\t\t\tNODESTOEXECUTE=$(add_node \"$(node)\" \"${NODESTOEXECUTE}\")\n\n\t\t\t\t# if SequenceCommand is \"Alexa.DeviceControls.Volume\" we have to adjust the local volume cache\n\t\t\t\tif [ \"$SEQUENCECMD\" = \"Alexa.DeviceControls.Volume\" ] ; then\n\t\t\t\t\tVOL=${SEQUENCEVAL%\\\\\\\"}\n\t\t\t\t\tVOL=${VOL##*\\\\\\\"}\n\t\t\t\t\tif [ $VOL -gt 0 ] ; then\n\t\t\t\t\t\techo $VOL false > \"${TMP}/.alexa.volume.${DEVICESERIALNUMBER}\"\n\t\t\t\t\telse\n\t\t\t\t\t\techo 0 true > \"${TMP}/.alexa.volume.${DEVICESERIALNUMBER}\"\n\t\t\t\t\tfi\n\t\t\t\t# add volume setting per device - the WHA volume is unrelyable\n\t\t\t\t# don't set volume if Alexa.Music.PlaySearchPhrase is used\n\t\t\t\telif [ \\( $SPEAKVOL -gt 0 -o -n \"${DEVICEVOLSPEAK}\" \\) -a \"${SEQUENCECMD}\" != \"Alexa.Music.PlaySearchPhrase\" ] ; then\n\t\t\t\t\tDEVICE=$(grep \"${DEVICESERIALNUMBER}\" ${DEVLIST}.txt | cut -d'=' -f 1)\n\t\t\t\t\tget_volumes\n\t\t\t\t\tVOLUMEPRENODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\\\"value\\\":\\\"'${SVOL}'\\\"') ${VOLUMEPRENODESTOEXECUTE})\n\t\t\t\t\tVOLUMEPOSTNODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\\\"value\\\":\\\"'${VOL}'\\\"') ${VOLUMEPOSTNODESTOEXECUTE})\n\t\t\t\tfi\n\t\t\tdone\n\n\t\t\tif [ -z \"${NODESTOEXECUTE}\" ] ; then\n\t\t\t\techo \"No clusterMembers found for command: ${COMMAND} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} family:${DEVICEFAMILY}\"\n\t\t\t\treturn\n\t\t\tfi\n\t\telse\n\t\t\tNODESTOEXECUTE=$(add_node \"$(node)\" \"${NODESTOEXECUTE}\")\n\n\t\t\tif [ \"$SEQUENCECMD\" = \"Alexa.DeviceControls.Volume\" ] ; then\n\t\t\t\tVOL=${SEQUENCEVAL%\\\\\\\"}\n\t\t\t\tVOL=${VOL##*\\\\\\\"}\n\t\t\t\tif [ $VOL -gt 0 ] ; then\n\t\t\t\t\techo $VOL false > \"${TMP}/.alexa.volume.${DEVICESERIALNUMBER}\"\n\t\t\t\telse\n\t\t\t\t\techo 0 true > \"${TMP}/.alexa.volume.${DEVICESERIALNUMBER}\"\n\t\t\t\tfi\n\t\t\t# don't set volume if Alexa.Music.PlaySearchPhrase is used\n\t\t\telif [ \\( $SPEAKVOL -gt 0 -o -n \"${DEVICEVOLSPEAK}\" \\) -a \"${SEQUENCECMD}\" != \"Alexa.Music.PlaySearchPhrase\" ] ; then\n\t\t\t\tget_volumes\n\t\t\t\tVOLUMEPRENODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\\\"value\\\":\\\"'${SVOL}'\\\"') ${VOLUMEPRENODESTOEXECUTE})\n\t\t\t\tVOLUMEPOSTNODESTOEXECUTE=$(add_node $(node Alexa.DeviceControls.Volume ',\\\"value\\\":\\\"'${VOL}'\\\"') ${VOLUMEPOSTNODESTOEXECUTE})\n\t\t\tfi\n\t\tfi\n\n\t\tif [ -n \"${VOLUMEPRENODESTOEXECUTE}\" -a -n \"${VOLUMEPOSTNODESTOEXECUTE}\" ] ; then\n\t\t\t# execute serially \"set_speak_volume\" => \"sequence_command\" => \"set_normal_volume\"\n\t\t\t#  (each subtask is executed in parallel)\n\t\t\tALEXACMD='{\"behaviorId\":\"PREVIEW\",\"sequenceJson\":\"{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.Sequence\\\",\\\"startNode\\\":{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.SerialNode\\\",\\\"nodesToExecute\\\":[{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.ParallelNode\\\",\\\"nodesToExecute\\\":['${VOLUMEPRENODESTOEXECUTE}']},{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.ParallelNode\\\",\\\"nodesToExecute\\\":['${NODESTOEXECUTE}']},{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.ParallelNode\\\",\\\"nodesToExecute\\\":['${VOLUMEPOSTNODESTOEXECUTE}']}]}}\",\"status\":\"ENABLED\"}'\n\t\telse\n\t\t\t# execute in parallel \"sequence_command\"\n\t\t\tALEXACMD='{\"behaviorId\":\"PREVIEW\",\"sequenceJson\":\"{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.Sequence\\\",\\\"startNode\\\":{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.ParallelNode\\\",\\\"nodesToExecute\\\":['${NODESTOEXECUTE}']}}\",\"status\":\"ENABLED\"}'\n\t\tfi\n\tfi\n\n\t# Due to some weird shell-escape-behavior the command has to be written to a file before POSTing it\n\techo $ALEXACMD > \"${TMP}/.alexa.cmd\"\n\n\t${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d @\"${TMP}/.alexa.cmd\" \\\n\t \"https://${ALEXA}/api/behaviors/preview\"\n\n\trm -f \"${TMP}/.alexa.cmd\"\nelse\n\t${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d ${COMMAND}\\\n\t \"https://${ALEXA}/api/np/command?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\"\nfi\n}\n\n#\n# play TuneIn radio station\n#\nplay_radio()\n{\n JSON='{\"contentToken\":\"music:'$(echo '[\"music/tuneIn/stationId\",\"'${STATIONID}'\"]|{\"previousPageId\":\"TuneIn_SEARCH\"}'| base64 -w 0| base64 -w 0 )'\"}'\n\n ${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X PUT -d \"${JSON}\" \\\n \"https://${ALEXA}/api/entertainment/v1/player/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\"\n}\n\n#\n# play library track\n#\nplay_song()\n{\n\tif [ -z \"${ALBUM}\" ] ; then\n\t\tJSON=\"{\\\"trackId\\\":\\\"${SONG}\\\",\\\"playQueuePrime\\\":true}\"\n\telse\n\t\tJSON=\"{\\\"albumArtistName\\\":\\\"${ARTIST}\\\",\\\"albumName\\\":\\\"${ALBUM}\\\"}\"\n\tfi\n\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"${JSON}\"\\\n \"https://${ALEXA}/api/cloudplayer/queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}&shuffle=false\"\n}\n\n#\n# play library playlist\n#\nplay_playlist()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"playlistId\\\":\\\"${PLIST}\\\",\\\"playQueuePrime\\\":true}\"\\\n \"https://${ALEXA}/api/cloudplayer/queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}&shuffle=false\"\n}\n\n#\n# play PRIME playlist\n#\nplay_prime_playlist()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"asin\\\":\\\"${ASIN}\\\"}\"\\\n \"https://${ALEXA}/api/prime/prime-playlist-queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}\"\n}\n\n#\n# play PRIME station\n#\nplay_prime_station()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"seed\\\":\\\"{\\\\\\\"type\\\\\\\":\\\\\\\"KEY\\\\\\\",\\\\\\\"seedId\\\\\\\":\\\\\\\"${SEEDID}\\\\\\\"}\\\",\\\"stationName\\\":\\\"none\\\",\\\"seedType\\\":\\\"KEY\\\"}\"\\\n \"https://${ALEXA}/api/gotham/queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}\"\n}\n\n#\n# play PRIME historical queue\n#\nplay_prime_hist_queue()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"deviceType\\\":\\\"${DEVICETYPE}\\\",\\\"deviceSerialNumber\\\":\\\"${DEVICESERIALNUMBER}\\\",\\\"mediaOwnerCustomerId\\\":\\\"${MEDIAOWNERCUSTOMERID}\\\",\\\"queueId\\\":\\\"${HIST}\\\",\\\"service\\\":null,\\\"trackSource\\\":\\\"TRACK\\\"}\"\\\n \"https://${ALEXA}/api/media/play-historical-queue\"\n}\n\n#\n# show library tracks\n#\nshow_library()\n{\n\tOFFSET=\"\";\n\tSIZE=50;\n\tTOTAL=0;\n\tFILE=${TMP}/.alexa.${TYPE}.list\n\n\tif [ ! -f ${FILE} ] ; then\n\t\techo -n '{\"playlist\":{\"entryList\":[' > ${FILE}\n\n\t\twhile [ 50 -le ${SIZE} ] ; do\n\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/cloudplayer/playlists/${TYPE}-V0-OBJECTID?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&size=${SIZE}&offset=${OFFSET}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}\" > ${FILE}.tmp\n\n\t\t\tOFFSET=$(${JQ} -r '.nextResultsToken' ${FILE}.tmp)\n\t\t\tSIZE=$(${JQ} -r '.playlist | .trackCount' ${FILE}.tmp)\n\t\t\t${JQ} -r -c '.playlist | .entryList' ${FILE}.tmp >> ${FILE}\n\t\t\techo \",\" >> ${FILE}\n\t\t\tTOTAL=$((TOTAL+SIZE))\n\t\tdone\n\t\techo \"[]],\\\"trackCount\\\":\\\"${TOTAL}\\\"}}\" >> ${FILE}\n\t\trm -f ${FILE}.tmp\n\tfi\n\t${JQ} -r '.playlist.trackCount' ${FILE}\n\t${JQ} '.playlist.entryList[] | .[]' ${FILE}\n}\n\n#\n# show Prime stations and playlists\n#\nshow_prime()\n{\n\tFILE=${TMP}/.alexa.${PRIME}.list\n\n\tif [ ! -f ${FILE} ] ; then\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/prime/{$PRIME}?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}\" > ${FILE}\n\n\t\tif [ \"$PRIME\" = \"prime-playlist-browse-nodes\" ] ; then\n\t\t\tfor I in $(${JQ} -r '.primePlaylistBrowseNodeList[].subNodes[].nodeId' ${FILE} 2>/dev/null) ; do\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/prime/prime-playlists-by-browse-node?browseNodeId=${I}&deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}\" >> ${FILE}\n\t\t\tdone\n\t\tfi\n\tfi\n\t${JQ} '.' ${FILE}\n}\n\n#\n# current queue\n#\nshow_queue()\n{\n\tPARENT=\"\"\n\tPARENTID=$(${JQ} --arg device \"${DEVICE}\" -r '.devices[] | select(.accountName == $device) | .parentClusters[0]' ${DEVLIST}.json)\n\tif [ \"$PARENTID\" != \"null\" ] ; then\n\t\tPARENTDEVICE=$(${JQ} --arg serial ${PARENTID} -r '.devices[] | select(.serialNumber == $serial) | .deviceType' ${DEVLIST}.json)\n\t\tPARENT=\"&lemurId=${PARENTID}&lemurDeviceType=${PARENTDEVICE}\"\n\tfi\n\n ${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n  -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n  -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n  \"https://${ALEXA}/api/np/player?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}${PARENT}\" | ${JQ} '.'\n\n ${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n  -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n  -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n  \"https://${ALEXA}/api/media/state?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\" | ${JQ} '.'\n\n ${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n  -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n  -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n  \"https://${ALEXA}/api/np/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\" | ${JQ} '.'\n}\n\nget_music_channels()\n{\n   ${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n    -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n    -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n    \"https://${ALEXA}/api/behaviors/entities?skillId=amzn1.ask.1p.music\" | ${JQ} -r '.[] | select( .supportedProperties[] == \"Alexa.Music.PlaySearchPhrase\" ) |  \"\\(.id) - \\(.displayName) \\(.description)\"'\n}\n\n#\n# device specific SPEAKVOL/NORMALVOL (sets SVOL/VOL)\n#\nget_volumes()\n{\n\tVOL=\"\"\n\tSVOL=\"\"\n\n\t# Not using arrays here in order to be compatible with non-Bash\n\t# Get the list position of the current device type\n\tIDX=0\n\tfor D in $DEVICEVOLNAME ; do\n\t\tif [ \"${D}\" = \"${DEVICE}\" ] ; then\n\t\t\tbreak;\n\t\tfi\n\t\tIDX=$((IDX+1))\n\tdone\n\n\t# get the speak volume at that position\n\tC=0\n\tfor D in $DEVICEVOLSPEAK ; do\n\t\tif [ $C -eq $IDX ] ; then\n\t\t\tif [ -n \"${D}\" ] ; then SVOL=$D ; fi \n\t\t\tbreak\n\t\tfi\n\t\tC=$((C+1))\n\tdone\n\tif [ -z \"${SVOL}\" ] ; then\n\t\tSVOL=$SPEAKVOL\n\tfi\n\n\t# try to retrieve the \"currently playing\" volume\n\tVOLMAXAGE=1\n\tVOL=$(get_volume)\n\n\tif [ -z \"${VOL}\" ] ; then\n\t\t# get the normal volume of the current device type\n\t\tC=0\n\t\tfor D in $DEVICEVOLNORMAL; do\n\t\t\tif [ $C -eq $IDX ] ; then\n\t\t\t\tVOL=$D\n\t\t\t\tbreak\n\t\t\tfi\n\t\t\tC=$((C+1))\n\t\tdone\n\t\t# if the volume is still undefined, use $NORMALVOL\n\t\tif [ -z \"${VOL}\" ] ; then\n\t\t\tVOL=$NORMALVOL\n\t\tfi\n\tfi\n\n}\n\n#\n# current volume level\n#\nget_volume()\n{\n\tVOLFILE=$(find \"${TMP}/.alexa.volume.${DEVICESERIALNUMBER}\" -mmin -${VOLMAXAGE} 2>/dev/null)\n\tif [ -z \"${VOLFILE}\" ] ; then\n\t\tVOL=$(${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t\t\t\t-H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t\t\t\t-H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n\t\t\t\t\"https://${ALEXA}/api/devices/deviceType/dsn/audio/v1/allDeviceVolumes\" | ${JQ} -r  --arg device \"${DEVICESERIALNUMBER}\" '.volumes[] | \"\\(.dsn) \\(.speakerVolume) \\(.speakerMuted)\"')\n\n\t\tif [ -n \"${VOL}\" ] ; then\n\t\t\t# write volume and mute state to file\n\t\t\tOIFS=$IFS\n\t\t\tIFS='\n'\n\t\t\tset -o noglob\n\t\t\tfor LINE in $VOL ; do\n\t\t\t\tSERIAL=$(echo \"${LINE}\" | cut -d' ' -f1)\n\t\t\t\tVOLUME=$(echo \"${LINE}\" | cut -d' ' -f2)\n\t\t\t\tMUTED=$(echo \"${LINE}\" | cut -d' ' -f3)\n\t\t\t\techo \"${VOLUME} ${MUTED}\" > \"${TMP}/.alexa.volume.${SERIAL}\"\n\t\t\tdone\n\t\t\tIFS=$OIFS\n\t\t\tcut -d' ' -f1 \"${TMP}/.alexa.volume.${DEVICESERIALNUMBER}\"\n\t\tfi\n\telse\n\t\tcut -d' ' -f1 \"${TMP}/.alexa.volume.${DEVICESERIALNUMBER}\"\n\tfi\n}\n\n#\n# show notifications and alarms\n#\nshow_notifications()\n{\n\techo \"/api/notifications\"\n ${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/notifications?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\"\n\techo\n}\n\n#\n# deletes a multiroom device\n#\ndelete_multiroom()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X DELETE \\\n \"https://${ALEXA}/api/lemur/tail/${DEVICESERIALNUMBER}\"\n}\n\n#\n# creates a multiroom device\n#\ncreate_multiroom()\n{\n\tJSON=\"{\\\"id\\\":null,\\\"name\\\":\\\"${LEMUR}\\\",\\\"members\\\":[\"\n\tfor DEVICE in $CHILD ; do\n\t\tset_var\n\t\tJSON=\"${JSON}{\\\"dsn\\\":\\\"${DEVICESERIALNUMBER}\\\",\\\"deviceType\\\":\\\"${DEVICETYPE}\\\"},\"\n\tdone\n\tJSON=\"${JSON%,}]}\"\n\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"${JSON}\" \\\n \"https://${ALEXA}/api/lemur/tail\"\n}\n\n#\n# list bluetooth devices\n#\nlist_bluetooth()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/bluetooth?cached=false\" | ${JQ} --arg serial \"${DEVICESERIALNUMBER}\" -r '.bluetoothStates[] | select(.deviceSerialNumber == $serial) | \"\\(.pairedDeviceList[]?.address) \\(.pairedDeviceList[]?.friendlyName)\"'\n}\n\n#\n# connect bluetooth device\n#\nconnect_bluetooth()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"bluetoothDeviceAddress\\\":\\\"${BLUETOOTH}\\\"}\"\\\n \"https://${ALEXA}/api/bluetooth/pair-sink/${DEVICETYPE}/${DEVICESERIALNUMBER}\"\n}\n\n#\n# disconnect bluetooth device\n#\ndisconnect_bluetooth()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST \\\n \"https://${ALEXA}/api/bluetooth/disconnect-sink/${DEVICETYPE}/${DEVICESERIALNUMBER}\"\n}\n\n#\n# get activity CSRF token\n#\nget_activity_csrf()\n{\n\t${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n \t -H \"Content-Type: application/json; charset=UTF-8\" \\\n \t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET\\\n\t \"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\n}\n\n#\n# get customer history records\n#\nget_history()\n{\n\tif ! [ -f ${TMP}/.alexa.activity.csrf ] ; then\n\t\tget_activity_csrf\n\tfi\n\n\tRES=$(${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L -w \"%{http_code}\" \\\n\t \t -H \"Content-Type: application/json; charset=UTF-8\" -H \"anti-csrftoken-a2z: $(cat ${TMP}/.alexa.activity.csrf)\" \\\n\t \t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d '{\"previousRequestToken\": null}'\\\n\t\t \"https://www.${AMAZON}/alexa-privacy/apd/rvh/customer-history-records-v2/?startTime=0&endTime=2147483647000&pageType=VOICE_HISTORY\" -o ${TMP}/.alexa.activity.json)\n\n\t# try again in case CSRF timed out\n\tif [ $RES -ne 200 ] ; then\n\t\tif [ -z \"${try}\" ] ; then\n\t\t\ttry=1\n\t\t\trm -f ${TMP}/.alexa.activity.csrf\n\t\t\tget_history\n\t\telse\n\t\t\techo \"ERROR: unable to retrieve customer history records\"\n\t\t\texit 1\n\t\tfi\n\tfi\n}\n\n#\n# device that sent the last command\n#\nlast_alexa()\n{\n\tget_history\n\t${JQ} -r '.customerHistoryRecords | sort_by(.timestamp) | reverse | .[0] | .recordKey' ${TMP}/.alexa.activity.json | cut -d'#' -f4 | xargs -i grep -m 1 {} ${DEVLIST}.txt\n}\n#\n# last command or last command of a specific device\n#\nlast_command()\n{\n\tget_history\n\n\tif [ -z \"$DEVICE\" ] ; then\n\t\t${JQ} -r --arg device \"$DEVICE\" '.customerHistoryRecords | sort_by(.timestamp) | reverse | .[0] | .voiceHistoryRecordItems | map({key: .recordItemType, value: .transcriptText})' ${TMP}/.alexa.activity.json\n\telse\n\t\t${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\n\tfi\n}\n\n#\n# logout\n#\nlog_off()\n{\n${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n https://${ALEXA}/logout > /dev/null\n\nrm -f ${DEVLIST}.json\nrm -f ${DEVLIST}.txt\nrm -f ${DEVLIST}_wha.txt\nrm -f ${COOKIE}\nrm -f ${TMP}/.alexa.*.list\nrm -f ${TMP}/.alexa.volume.*\n}\n\nif [ -z \"$LASTALEXA\" -a -z \"$LASTCOMMAND\" -a -z \"$CHANNEL\" -a -z \"$BLUETOOTH\" -a -z \"$LEMUR\" -a -z \"$PLIST\" -a -z \"$HIST\" -a -z \"$SEEDID\" -a -z \"$ASIN\" -a -z \"$PRIME\" -a -z \"$TYPE\" -a -z \"$QUEUE\" -a -z \"$NOTIFICATIONS\" -a -z \"$LIST\" -a -z \"$COMMAND\" -a -z \"$STATIONID\" -a -z \"$SONG\" -a -z \"$GETVOL\" -a -n \"$LOGOFF\" ] ; then\n\techo \"only logout option present, logging off ...\"\n\tlog_off\n\texit 0\nfi\n\nif [ ! -f ${COOKIE} ] ; then\n\techo \"cookie does not exist. logging in ...\"\n\tlog_in\nfi\n\ncheck_status\nif [ $? -eq 0 ] ; then\n\techo \"cookie expired, logging in again ...\"\n\tlog_in\n\tcheck_status\n\tif [ $? -eq 0 ] ; then\n\t\techo \"log in failed, aborting\"\n\t\texit 1\n\tfi\nfi\n\nif [ ! -f ${DEVLIST}.json -o ! -f ${DEVLIST}.txt ] ; then\n\techo \"device list does not exist. downloading ...\"\n\tget_devlist\n\tif [ ! -f ${DEVLIST}.json ] ; then\n\t\techo \"failed to download device list, aborting\"\n\t\texit 1\n\tfi\nfi\n\nif [ -n \"$LOGIN\" ] ; then\n\techo \"logged in\"\n\texit 0\nfi\n\nif [ -n \"$CHANNEL\" ] ; then\n\tget_music_channels\n\texit 0\nfi\n\nif [ -n \"$COMMAND\" -o -n \"$QUEUE\" -o -n \"$NOTIFICATIONS\" -o -n \"$GETVOL\" ] ; then\n\tif [ \"${DEVICE}\" = \"ALL\" ] ; then\n\t\tfor 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\n\t\t\tset_var\n\t\t\tif [ -n \"$COMMAND\" ] ; then\n\t\t\t\techo \"sending cmd:${COMMAND} to dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} customerid:${MEDIAOWNERCUSTOMERID}\"\n\t\t\t\trun_cmd\n\t\t\t\t# in order to prevent a \"Rate exceeded\" we need to delay the command\n\t\t\t\tsleep 1\n\t\t\t\techo\n\t\t\telif [ -n \"$GETVOL\" ] ; then\n\t\t\t\techo \"get volume for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\t\tget_volume\n\t\t\telif [ -n \"$NOTIFICATIONS\" ] ; then\n\t\t\t\techo \"notifications info for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\t\tshow_notifications\n\t\t\telse\n\t\t\t\techo \"queue info for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\t\tshow_queue\n\t\t\t\techo\n\t\t\tfi\n\t\tdone\n\telse\n\t\tset_var\n\t\tif [ -n \"$COMMAND\" ] ; then\n\t\t\techo \"sending cmd:${COMMAND} to dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} customerid:${MEDIAOWNERCUSTOMERID}\"\n\t\t\trun_cmd\n\t\t\techo\n\t\telif [ -n \"$GETVOL\" ] ; then\n\t\t\techo \"get volume for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\tget_volume\n\t\telif [ -n \"$NOTIFICATIONS\" ] ; then\n\t\t\techo \"notifications info for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\tshow_notifications\n\t\telse\n\t\t\techo \"queue info for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\tshow_queue\n\t\t\techo\n\t\tfi\n\tfi\nelif [ -n \"$LEMUR\" ] ; then\n\tDEVICESERIALNUMBER=$(${JQ} --arg device \"${LEMUR}\" -r '.devices[] | select(.accountName == $device and .deviceFamily == \"WHA\") | .serialNumber' ${DEVLIST}.json)\n\tif [ -n \"$DEVICESERIALNUMBER\" ] ; then\n\t\tdelete_multiroom\n\telse\n\t\tif [ -z \"$CHILD\" ] ; then\n\t\t\techo \"ERROR: ${LEMUR} is no multiroom device. Cannot delete ${LEMUR}\".\n\t\t\texit 1\n\t\tfi\n\tfi\n\tif [ -z \"$CHILD\" ] ; then\n\t\techo \"Deleted multi room dev:${LEMUR} serial:${DEVICESERIALNUMBER}\"\n\telse\n\t\techo \"Creating multi room dev:${LEMUR} member_dev(s):${CHILD}\"\n\t\tcreate_multiroom\n\t\techo\n\tfi\n\trm -f ${DEVLIST}.json\n\trm -f ${DEVLIST}.txt\n\trm -f ${DEVLIST}_wha.txt\n\tget_devlist\nelif [ -n \"$BLUETOOTH\" ] ; then\n\tif [ \"$BLUETOOTH\" = \"list\" -o \"$BLUETOOTH\" = \"List\" -o \"$BLUETOOTH\" = \"LIST\" ] ; then\n\t\tif [ \"${DEVICE}\" = \"ALL\" ] ; then\n\t\t\tfor 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\n\t\t\t\tset_var\n\t\t\t\techo \"bluetooth devices for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}:\"\n\t\t\t\tlist_bluetooth\n\t\t\t\techo\n\t\t\tdone\n\t\telse\n\t\t\tset_var\n\t\t\techo \"bluetooth devices for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}:\"\n\t\t\tlist_bluetooth\n\t\t\techo\n\t\tfi\n\telif [ \"$BLUETOOTH\" = \"null\" ] ; then\n\t\tset_var\n\t\techo \"disconnecting dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} from bluetooth\"\n\t\tdisconnect_bluetooth\n\t\techo\n\telse\n\t\tset_var\n\t\techo \"connecting dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} to bluetooth device:${BLUETOOTH}\"\n\t\tconnect_bluetooth\n\t\techo\n\tfi\nelif [ -n \"$STATIONID\" ] ; then\n\tset_var\n\techo \"playing stationID:${STATIONID} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} mediaownerid:${MEDIAOWNERCUSTOMERID}\"\n\tplay_radio\nelif [ -n \"$SONG\" ] ; then\n\tset_var\n\techo \"playing library track:${SONG} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} mediaownerid:${MEDIAOWNERCUSTOMERID}\"\n\tplay_song\nelif [ -n \"$PLIST\" ] ; then\n\tset_var\n\techo \"playing library playlist:${PLIST} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} mediaownerid:${MEDIAOWNERCUSTOMERID}\"\n\tplay_playlist\nelif [ -n \"$LIST\" ] ; then\n\techo \"the following devices exist in your account:\"\n\tlist_devices\nelif [ -n \"$TYPE\" ] ; then\n\tset_var\n\techo -n \"the following songs exist in your ${TYPE} library: \"\n\tshow_library\nelif [ -n \"$PRIME\" ] ; then\n\tset_var\n\techo \"the following songs exist in your PRIME ${PRIME}:\"\n\tshow_prime\nelif [ -n \"$ASIN\" ] ; then\n\tset_var\n\techo \"playing PRIME playlist ${ASIN}\"\n\tplay_prime_playlist\nelif [ -n \"$SEEDID\" ] ; then\n\tset_var\n\techo \"playing PRIME station ${SEEDID}\"\n\tplay_prime_station\nelif [ -n \"$HIST\" ] ; then\n\tset_var\n\techo \"playing PRIME historical queue ${HIST}\"\n\tplay_prime_hist_queue\nelif [ -n \"$LASTALEXA\" ] ; then\n\tlast_alexa\nelif [ -n \"$LASTCOMMAND\" ] ; then\n\tlast_command\nelse\n\techo \"no alexa command received\"\nfi\n\nif [ -n \"$LOGOFF\" ] ; then\n\techo \"logout option present, logging off ...\"\n\tlog_off\nfi\n"
  },
  {
    "path": "alexa_remote_control_plain.sh",
    "content": "#!/bin/sh\n#\n# Amazon Alexa Remote Control (PLAIN shell)\n#  alex(at)loetzimmer.de\n#\n# 2021-01-28: v0.17c (for updates see http://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html)\n# 2021-09-02: v0.17d includes fixes for playing tunein (base64 required)\n#\n# !!! THIS IS THE FINAL VERSION !!!\n#\n#  Due to JQ being widely available across platforms there is no need to expose oneself to the hacks\n#  required when parsing JSON with BASH.\n#\n###\n#\n# (no BASHisms were used, should run with any shell)\n# - requires cURL for web communication\n# - (GNU) sed and awk for extraction\n# - base64 for B64 encoding (make sure \"-w 0\" option is available on your platform)\n# - oathtool as OATH one-time password tool (optional for two-factor authentication)\n#\n##########################################\n\nSET_EMAIL='amazon_account@email.address'\nSET_PASSWORD='Very_Secret_Amazon_Account_Password'\nSET_MFA_SECRET=''\n# something like:\n#  1234 5678 9ABC DEFG HIJK LMNO PQRS TUVW XYZ0 1234 5678 9ABC DEFG\n\nSET_LANGUAGE='de,en-US;q=0.7,en;q=0.3'\n#SET_LANGUAGE='en-US'\n\nSET_TTS_LOCALE='de-DE'\n\nSET_AMAZON='amazon.de'\n#SET_AMAZON='amazon.com'\n\nSET_ALEXA='alexa.amazon.de'\n#SET_ALEXA='pitangui.amazon.com'\n\n# cURL binary\nSET_CURL='/usr/bin/curl'\n\n# cURL options\n#  -k : if your cURL cannot verify CA certificates, you'll have to trust any\n#  --compressed : if your cURL was compiled with libz you may use compression\n#  --http1.1 : cURL defaults to HTTP/2 on HTTPS connections if available\nSET_OPTS='--compressed --http1.1'\n#SET_OPTS='-k --compressed --http1.1'\n\n# browser identity\nSET_BROWSER='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:1.0) bash-script/1.0'\n#SET_BROWSER='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0'\n\n# oathtool command line tool\nSET_OATHTOOL='/usr/bin/oathtool'\n\n# tmp path\nSET_TMP=\"/tmp\"\n\n# Volume for speak commands (a SPEAKVOL of 0 leaves the volume settings untouched)\nSET_SPEAKVOL=\"0\"\n# if no current playing volume can be determined, fall back to normal volume\nSET_NORMALVOL=\"10\"\n\n# Device specific volumes (overriding the above)\nSET_DEVICEVOLNAME=\"EchoDot2ndGen Echo1stGen\"\nSET_DEVICEVOLSPEAK=\"100 30\"\nSET_DEVICEVOLNORMAL=\"100 20\"\n\n###########################################\n# nothing to configure below here\n#\n\n# retrieving environment variables if any are set\nEMAIL=${EMAIL:-$SET_EMAIL}\nPASSWORD=${PASSWORD:-$SET_PASSWORD}\nMFA_SECRET=${MFA_SECRET:-$SET_MFA_SECRET}\nAMAZON=${AMAZON:-$SET_AMAZON}\nALEXA=${ALEXA:-$SET_ALEXA}\nLANGUAGE=${LANGUAGE:-$SET_LANGUAGE}\nBROWSER=${BROWSER:-$SET_BROWSER}\nCURL=${CURL:-$SET_CURL}\nOPTS=${OPTS:-$SET_OPTS}\nTTS_LOCALE=${TTS_LOCALE:-$SET_TTS_LOCALE}\nTMP=${TMP:-$SET_TMP}\nOATHTOOL=${OATHTOOL:-$SET_OATHTOOL}\nSPEAKVOL=${SPEAKVOL:-$SET_SPEAKVOL}\nNORMALVOL=${NORMALVOL:-$SET_NORMALVOL}\nDEVICEVOLNAME=${DEVICEVOLNAME:-$SET_DEVICEVOLNAME}\nDEVICEVOLSPEAK=${DEVICEVOLSPEAK:-$SET_DEVICEVOLSPEAK}\nDEVICEVOLNORMAL=${DEVICEVOLNORMAL:-$SET_DEVICEVOLNORMAL}\n\nCOOKIE=\"${TMP}/.alexa.cookie\"\nDEVLIST=\"${TMP}/.alexa.devicelist.json\"\nDEVTXT=\"${TMP}/.alexa.devicelist.txt\"\nDEVALL=\"${TMP}/.alexa.devicelist.all\"\n\nGUIVERSION=0\n\nLIST=\"\"\nLOGOFF=\"\"\nCOMMAND=\"\"\nTTS=\"\"\nSEQUENCECMD=\"\"\nSEQUENCEVAL=\"\"\nSTATIONID=\"\"\nQUEUE=\"\"\nSONG=\"\"\nALBUM=\"\"\nARTIST=\"\"\nASIN=\"\"\nSEEDID=\"\"\nHIST=\"\"\nLEMUR=\"\"\nCHILD=\"\"\nPLIST=\"\"\nBLUETOOTH=\"\"\nLASTALEXA=\"\"\nNOTIFICATIONS=\"\"\n\nusage()\n{\n\techo \"$0 [-d <device>|ALL] -e <pause|play|next|prev|fwd|rwd|shuffle|repeat|vol:<0-100>> |\"\n\techo \"          -b [list|<\\\"AA:BB:CC:DD:EE:FF\\\">] | -q | -n | -r <\\\"station name\\\"|stationid> |\"\n\techo \"          -s <trackID|'Artist' 'Album'> | -t <ASIN> | -u <seedID> | -v <queueID> | -w <playlistId> |\"\n\techo \"          -a | -m <multiroom_device> [device_1 .. device_X] | -lastalexa | -l | -h\"\n\techo\n\techo \"   -e : run command, additional SEQUENCECMDs:\"\n\techo \"        weather,traffic,flashbriefing,goodmorning,singasong,tellstory,\"\n\techo \"        speak:'<text>',sound:<soundeffect_name>,\"\n\techo \"        textcommand:'<anything you would otherwise say to Alexa>'\"\n\techo \"   -b : connect/disconnect/list bluetooth device\"\n\techo \"   -q : query queue\"\n\techo \"   -n : query notifications\"\n\techo \"   -r : play tunein radio\"\n\techo \"   -s : play library track/library album\"\n\techo \"   -t : play Prime playlist\"\n\techo \"   -u : play Prime station\"\n\techo \"   -v : play Prime historical queue\"\n\techo \"   -w : play library playlist\"\n\techo \"   -a : list available devices\"\n\techo \"   -m : delete multiroom and/or create new multiroom containing devices\"\n\techo \"   -lastalexa : print serial number that received the last voice command\"\n\techo \"   -login : Logs in, without further command\"\n\techo \"   -l : logoff\"\n\techo \"   -h : help\"\n}\n\nwhile [ \"$#\" -gt 0 ] ; do\n\tcase \"$1\" in\n\t\t--version)\n\t\t\techo \"v0.17d\"\n\t\t\texit 0\n\t\t\t;;\n\t\t-d)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tDEVICE=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-e)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tCOMMAND=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-b)\n\t\t\tif [ \"${2#-}\" = \"${2}\" -a -n \"$2\" ] ; then\n\t\t\t\tBLUETOOTH=$2\n\t\t\t\tshift\n\t\t\telse\n\t\t\t\tBLUETOOTH=\"null\"\n\t\t\tfi\n\t\t\t;;\n\t\t-m)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tLEMUR=$2\n\t\t\tshift\n\t\t\twhile [ \"${2#-}\" = \"${2}\" -a -n \"$2\" ] ; do\n\t\t\t\tCHILD=\"${CHILD} ${2}\"\n\t\t\t\tshift\n\t\t\tdone\n\t\t\t;;\n\t\t-r)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tSTATIONID=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-s)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tSONG=$2\n\t\t\tshift\n\t\t\tif [ \"${2#-}\" = \"${2}\" -a -n \"$2\" ] ; then\n\t\t\t\tALBUM=$2\n\t\t\t\tARTIST=$SONG\n\t\t\t\tshift\n\t\t\tfi\n\t\t\t;;\n\t\t-t)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tASIN=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-u)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tSEEDID=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-v)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tHIST=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-w)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tPLIST=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-d)\n\t\t\tif [ \"${2#-}\" != \"${2}\" -o -z \"$2\" ] ; then\n\t\t\t\techo \"ERROR: missing argument for ${1}\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\tDEVICE=$2\n\t\t\tshift\n\t\t\t;;\n\t\t-login)\n\t\t\tLOGIN=\"true\"\n\t\t\t;;\n\t\t-l)\n\t\t\tLOGOFF=\"true\"\n\t\t\t;;\n\t\t-a)\n\t\t\tLIST=\"true\"\n\t\t\t;;\n\t\t-q)\n\t\t\tQUEUE=\"true\"\n\t\t\t;;\n\t\t-n)\n\t\t\tNOTIFICATIONS=\"true\"\n\t\t\t;;\n\t\t-lastalexa)\n\t\t\tLASTALEXA=\"true\"\n\t\t\t;;\n\t\t-h|-\\?|--help)\n\t\t\tusage\n\t\t\texit 0\n\t\t\t;;\n\t\t*)\n\t\t\techo \"ERROR: unknown option ${1}\"\n\t\t\tusage\n\t\t\texit 1\n\t\t\t;;\n\tesac\n\tshift\ndone\n\ncase \"$COMMAND\" in\n\tpause)\n\t\t\tCOMMAND='{\"type\":\"PauseCommand\"}'\n\t\t\t;;\n\tplay)\n\t\t\tCOMMAND='{\"type\":\"PlayCommand\"}'\n\t\t\t;;\n\tnext)\n\t\t\tCOMMAND='{\"type\":\"NextCommand\"}'\n\t\t\t;;\n\tprev)\n\t\t\tCOMMAND='{\"type\":\"PreviousCommand\"}'\n\t\t\t;;\n\tfwd)\n\t\t\tCOMMAND='{\"type\":\"ForwardCommand\"}'\n\t\t\t;;\n\trwd)\n\t\t\tCOMMAND='{\"type\":\"RewindCommand\"}'\n\t\t\t;;\n\tshuffle)\n\t\t\tCOMMAND='{\"type\":\"ShuffleCommand\",\"shuffle\":\"true\"}'\n\t\t\t;;\n\trepeat)\n\t\t\tCOMMAND='{\"type\":\"RepeatCommand\",\"repeat\":true}'\n\t\t\t;;\n\tvol:*)\n\t\t\tVOL=${COMMAND##*:}\n\t\t\t# volume as integer!\n\t\t\tif [ $VOL -le 100 -a $VOL -ge 0 ] ; then\n#\t\t\t\tCOMMAND='{\"type\":\"VolumeLevelCommand\",\"volumeLevel\":'${VOL}'}'\n\t\t\t\tSEQUENCECMD='Alexa.DeviceControls.Volume'\n\t\t\t\tSEQUENCEVAL=',\\\"value\\\":\\\"'${VOL}'\\\"'\n\t\t\telse\n\t\t\t\techo \"ERROR: volume should be an integer between 0 and 100\"\n\t\t\t\tusage\n\t\t\t\texit 1\n\t\t\tfi\n\t\t\t;;\n\ttextcommand:*)\n\t\t\tSEQUENCECMD='Alexa.TextCommand\\\",\\\"skillId\\\":\\\"amzn1.ask.1p.tellalexa'\n\t\t\tSEQUENCEVAL=$(echo ${COMMAND##textcommand:} | sed -r s/\\\"/\\'/g)\n\t\t\tSEQUENCEVAL=',\\\"text\\\":\\\"'${SEQUENCEVAL}'\\\"'\n\t\t\t;;\n\tspeak:*)\n\t\t\tTTS=$(echo ${COMMAND##*:} | sed -r 's/[\"\\\\]/ /g')\n\t\t\tTTS=',\\\"textToSpeak\\\":\\\"'${TTS}'\\\"'\n\t\t\tSEQUENCECMD='Alexa.Speak'\n\t\t\tSEQUENCEVAL=$TTS\n\t\t\t;;\n\tsound:*)\n\t\t\tSEQUENCECMD='Alexa.Sound'\n\t\t\tSEQUENCEVAL=',\\\"soundStringId\\\":\\\"'${COMMAND##sound:}'\\\"'\n\t\t\t;;\n\tweather)\n\t\t\tSEQUENCECMD='Alexa.Weather.Play'\n\t\t\t;;\n\ttraffic)\n\t\t\tSEQUENCECMD='Alexa.Traffic.Play'\n\t\t\t;;\n\tflashbriefing)\n\t\t\tSEQUENCECMD='Alexa.FlashBriefing.Play'\n\t\t\t;;\n\tgoodmorning)\n\t\t\tSEQUENCECMD='Alexa.GoodMorning.Play'\n\t\t\t;;\n\tsingasong)\n\t\t\tSEQUENCECMD='Alexa.SingASong.Play'\n\t\t\t;;\n\ttellstory)\n\t\t\tSEQUENCECMD='Alexa.TellStory.Play'\n\t\t\t;;\n\t\"\")\n\t\t\t;;\n\t*)\n\t\t\techo \"ERROR: unknown command \\\"${COMMAND}\\\"!\"\n\t\t\tusage\n\t\t\texit 1\n\t\t\t;;\nesac\n\n#\n# Amazon Login\n#\nlog_in()\n{\n################################################################\n#\n# following headers are required:\n#\tAccept-Language\t(possibly for determining login region)\n#\tUser-Agent\t(CURL wouldn't store cookies without)\n#\n################################################################\n\nrm -f ${DEVLIST}\nrm -f ${DEVTXT}\nrm -f ${DEVALL}\nrm -f ${COOKIE}\n\n#\n# get first cookie and write redirection target into referer\n#\n${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\\\n https://alexa.${AMAZON} | grep \"hidden\" | sed 's/hidden/\\n/g' | grep \"value=\\\"\" | sed -r 's/^.*name=\"([^\"]+)\".*value=\"([^\"]+)\".*/\\1=\\2\\&/g' > \"${TMP}/.alexa.postdata\"\n\n#\n# login empty to generate session\n#\n${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\\\n -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\"\n\n#\n# add OTP if using MFA\n#\nif [ -n \"${MFA_SECRET}\" ] ; then\n\tOTP=$(${OATHTOOL} -b --totp \"${MFA_SECRET}\")\n\tPASSWORD=\"${PASSWORD}${OTP}\"\nfi\n\n#\n# login with filled out form\n#  !!! referer now contains session in URL\n#\n${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\\\n -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\"\n\n# check whether the login has been successful or exit otherwise\nif [ -z \"$(grep 'Location: https://alexa.*html' ${TMP}/.alexa.header2)\" ] ; then\n\techo \"ERROR: Amazon Login was unsuccessful. Possibly you get a captcha login screen.\"\n\techo \" Try logging in to https://alexa.${AMAZON} with your browser. In your browser\"\n\techo \" make sure to have all Amazon related cookies deleted and Javascript disabled!\"\n\techo\n\techo \" (For more information have a look at ${TMP}/.alexa.login)\"\n\techo\n\techo \" To avoid issues with captcha, try using Multi-Factor Authentication.\"\n\techo \" To do so, first set up Two-Step Verification on your Amazon account, then\"\n\techo \" configure this script (or the environment) with your MFA secret.\"\n\techo \" Support for Multi-Factor Authentication requires 'oathtool' to be installed.\"\n\n\trm -f ${COOKIE}\n\trm -f \"${TMP}/.alexa.header\"\n\trm -f \"${TMP}/.alexa.header2\"\n\trm -f \"${TMP}/.alexa.postdata\"\n\trm -f \"${TMP}/.alexa.postdata2\"\n\texit 1\nfi\n\t\n#\n# get CSRF\n#\n${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n https://${ALEXA}/api/language > /dev/null\n\nif [ -z \"$(grep \".${AMAZON}.*csrf\" ${COOKIE})\" ] ; then\n\techo \"trying to get CSRF from handlebars\"\n\t${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t https://${ALEXA}/templates/oobe/d-device-pick.handlebars > /dev/null\nfi\n\nif [ -z \"$(grep \".${AMAZON}.*csrf\" ${COOKIE})\" ] ; then\n\techo \"trying to get CSRF from devices-v2\"\n\t${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t https://${ALEXA}/api/devices-v2/device?cached=false > /dev/null\nfi\n\nrm -f \"${TMP}/.alexa.login\"\nrm -f \"${TMP}/.alexa.header\"\nrm -f \"${TMP}/.alexa.header2\"\nrm -f \"${TMP}/.alexa.postdata\"\nrm -f \"${TMP}/.alexa.postdata2\"\n\nif [ -z \"$(grep \".${AMAZON}.*csrf\" ${COOKIE})\" ] ; then\n\techo \"ERROR: no CSRF cookie received\"\n\texit 1\nfi\n}\n\n#\n# get JSON device list\n#\nget_devlist()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\"\\\n \"https://${ALEXA}/api/devices-v2/device?cached=false\" > ${DEVLIST}\n \n\tif [ ! -f ${DEVTXT} ] ; then\n\t\tcat ${DEVLIST}| sed 's/\\\\\\\\\\//\\//g' | sed 's/[{}]//g' | awk -v k=\"text\" '{n=split($0,a,\",\"); for (i=1; i<=n; i++) print a[i]}' | sed 's/\\\"\\:\\\"/\\|/g' | sed 's/[\\,]/ /g' | sed 's/\\\"//g' > ${DEVTXT}\n\tfi\n\t\n\t# create a file that contains valid device names for the \"ALL\" device\n\tATTR=\"accountName\"\n\tNAME=$(grep ${ATTR}\\| ${DEVTXT} | sed \"s/^.*${ATTR}|//\" | sed 's/ /_/g')\n\n\tATTR=\"deviceFamily\"\n\tFAMILY=$(grep ${ATTR}\\| ${DEVTXT} | sed \"s/^.*${ATTR}|//\" | sed 's/ /_/g')\n\n\tIDX=0\n\tfor N in $NAME ; do\n\t\tC=0\n\t\tfor F in $FAMILY ; do\n\t\t\tif [ $C -eq $IDX ] ; then\n\t\t\t\tif [ \"$F\" = \"WHA\" -o \"$F\" = \"ECHO\" -o \"$F\" = \"KNIGHT\" -o \"$F\" = \"ROOK\" ] ; then\n\t\t\t\t\techo ${N} >> ${DEVALL}\n\t\t\t\tfi\n\t\t\t\tbreak\n\t\t\tfi\n\t\t\tC=$((C+1))\n\t\tdone\n\t\tIDX=$((IDX+1))\n\tdone\n}\n\ncheck_status()\n{\n#\n# bootstrap with GUI-Version writes GUI version to cookie\n#  returns among other the current authentication state\n#\n\tAUTHSTATUS=$(${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L https://${ALEXA}/api/bootstrap?version=${GUIVERSION})\n\tMEDIAOWNERCUSTOMERID=$(echo $AUTHSTATUS | sed -r 's/^.*\"customerId\":\"([^,]+)\",.*$/\\1/g')\n\tAUTHSTATUS=$(echo $AUTHSTATUS | sed -r 's/^.*\"authenticated\":([^,]+),.*$/\\1/g')\n\n\tif [ \"$AUTHSTATUS\" = \"true\" ] ; then\n\t\treturn 1\n\tfi\n\n\treturn 0\n}\n\n#\n# set device specific variables from JSON device list\n#\nset_var()\n{\n\tATTR=\"accountName\"\n\tNAME=$(grep ${ATTR}\\| ${DEVTXT} | sed \"s/^.*${ATTR}|//\" | sed 's/ /_/g')\n\n\tATTR=\"deviceType\"\n\tTYPE=$(grep ${ATTR}\\| ${DEVTXT} | sed \"s/^.*${ATTR}|//\" | sed 's/ /_/g')\n\n\tATTR=\"serialNumber\"\n\tSERIAL=$(grep ${ATTR}\\| ${DEVTXT} | sed \"s/^.*${ATTR}|//\" | sed 's/ /_/g')\n\n#\tATTR=\"deviceOwnerCustomerId\"\n#\tMEDIAID=`grep ${ATTR}\\| ${DEVTXT} | sed \"s/^.*${ATTR}|//\" | sed 's/ /_/g')\n\n\tATTR=\"deviceFamily\"\n\tFAMILY=$(grep ${ATTR}\\| ${DEVTXT} | sed \"s/^.*${ATTR}|//\" | sed 's/ /_/g')\n\n\tATTR=\"online\"\n\tONLINE=$(grep ${ATTR}\\: ${DEVTXT} | sed \"s/^.*${ATTR}://\")\n\n\tif [ -z \"${DEVICE}\" ] ; then\n\t\t# if no device was supplied, use the first Echo(dot) in device list\n        IDX=0\n\t\tfor F in $FAMILY ; do\n\t\t\tif [ \"$F\" = \"ECHO\" -o \"$F\" = \"KNIGHT\" -o \"$F\" = \"ROOK\" ] ; then\n\t\t\t\tbreak;\n\t\t\tfi\n\t\t\tIDX=$((IDX+1))\n\t\tdone\n\n\t\tC=0\n\t\tfor N in $NAME ; do\n\t\t\tif [ $C -eq $IDX ] ; then\n\t\t\t\tDEVICE=$N\n\t\t\t\tbreak\n\t\t\tfi\n\t\t\tC=$((C+1))\n\t\tdone\n\t\techo \"setting default device to:\"\n\t\techo ${DEVICE}\n\telse\n\t\tDEVICE=`echo $DEVICE | sed 's/ /_/g'`\n\t\tIDX=0\n\t\tfor N in $NAME ; do\n\t\t\tif [ \"$N\" = \"$DEVICE\" ] ; then\n\t\t\t\tbreak;\n\t\t\tfi\n\t\t\tIDX=$((IDX+1))\n\t\tdone\n\tfi\n\n# customerId is now retrieved from the logged in user\n# the customerId in the device list is always from the user registering the device initially\n#\tC=0\n#\tfor I in $MEDIAID ; do\n#\t\tif [ $C -eq $IDX ] ; then\n#\t\t\tMEDIAOWNERCUSTOMERID=$I\n#\t\t\tbreak\n#\t\tfi\n#\t\tC=$((C+1))\n#\tdone\n\n\tC=0\n\tfor T in $TYPE ; do\n\t\tif [ $C -eq $IDX ] ; then\n\t\t\tDEVICETYPE=$T\n\t\t\tbreak\n\t\tfi\n\t\tC=$((C+1))\n\tdone\n\n\tC=0\n\tfor S in $SERIAL ; do\n\t\tif [ $C -eq $IDX ] ; then\n\t\t\tDEVICESERIALNUMBER=$S\n\t\t\tbreak\n\t\tfi\n\t\tC=$((C+1))\n\tdone\n\n\tC=0\n\tfor F in $FAMILY ; do\n\t\tif [ $C -eq $IDX ] ; then\n\t\t\tDEVICEFAMILY=$F\n\t\t\tbreak\n\t\tfi\n\t\tC=$((C+1))\n\tdone\n\n\tC=0\n\tfor O in $ONLINE ; do\n\t\tif [ $C -eq $IDX ] ; then\n\t\t\tDEVICESTATE=$O\n\t\t\tbreak\n\t\tfi\n\t\tC=$((C+1))\n\tdone\n\n\tif [ -z \"${DEVICESERIALNUMBER}\" ] ; then\n\t\techo \"ERROR: unkown device dev:${DEVICE}\"\n\t\texit 1\n\tfi\n}\n\n#\n# execute command\n# (SequenceCommands by Michael Geramb and Ralf Otto)\n#\nrun_cmd()\n{\nif [ -n \"${SEQUENCECMD}\" ] ; then\n\tif echo $COMMAND | grep -q -E \"weather|traffic|flashbriefing|goodmorning|singasong|tellstory|speak|sound|textcommand\" ; then\n\t\tif [ \"${DEVICEFAMILY}\" = \"WHA\" ] ; then\n\t\t\techo \"Skipping unsupported command: ${COMMAND} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} family:${DEVICEFAMILY}\"\n\t\t\treturn\n\t\tfi\n\tfi\n\t# the speak command is treated differently if $SPEAKVOL > 0\n\t\tif [ -n \"${TTS}\" -a $SPEAKVOL -gt 0 ] || [ \"${COMMAND%%:*}\" = 'sound' -a $SPEAKVOL -gt 0 ] ; then\n\t\tSVOL=$SPEAKVOL\n\n\t\t# Not using arrays here in order to be compatible with non-Bash\n\t\t# Get the list position of the current device type\n\t\tIDX=0\n\t\tfor D in $DEVICEVOLNAME ; do\n\t\t\tif [ \"${D}\" = \"${DEVICE}\" ] ; then\n\t\t\t\tbreak;\n\t\t\tfi\n\t\t\tIDX=$((IDX+1))\n\t\tdone\n\n\t\t# get the speak volume at that position\n\t\tC=0\n\t\tfor D in $DEVICEVOLSPEAK ; do\n\t\t\tif [ $C -eq $IDX ] ; then\n\t\t\t\tif [ -n \"${D}\" ] ; then SVOL=$D ; fi \n\t\t\t\tbreak\n\t\t\tfi\n\t\t\tC=$((C+1))\n\t\tdone\n\n\t\t# try to retrieve the \"currently playing\" volume\n\t\tVOL=$(${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t\t -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t\t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n\t\t \"https://${ALEXA}/api/media/state?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\" | grep 'volume' | sed -r 's/^.*\"volume\":\\s*([0-9]+)[^0-9]*$/\\1/g')\n\n\t\t# in order to prevent a \"Rate exceeded\" we need to delay the command\n\t\tsleep 1\n\n\t\tif [ -z \"${VOL}\" ] ; then\n\t\t\t# get the normal volume of the current device type\n\t\t\tC=0\n\t\t\tfor D in $DEVICEVOLNORMAL; do\n\t\t\t\tif [ $C -eq $IDX ] ; then\n\t\t\t\t\tVOL=$D\n\t\t\t\t\tbreak\n\t\t\t\tfi\n\t\t\t\tC=$((C+1))\n\t\t\tdone\n\t\t\t# if the volume is still undefined, use $NORMALVOL\n\t\t\tif [ -z \"${VOL}\" ] ; then\n\t\t\t\tVOL=$NORMALVOL\n\t\t\tfi\n\t\tfi\n\n\t\tALEXACMD='{\"behaviorId\":\"PREVIEW\",\"sequenceJson\":\"{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.Sequence\\\",\\\"startNode\\\":{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.SerialNode\\\",\\\"nodesToExecute\\\":[{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\\\"type\\\":\\\"Alexa.DeviceControls.Volume\\\",\\\"operationPayload\\\":{\\\"deviceType\\\":\\\"'${DEVICETYPE}'\\\",\\\"deviceSerialNumber\\\":\\\"'${DEVICESERIALNUMBER}'\\\",\\\"customerId\\\":\\\"'${MEDIAOWNERCUSTOMERID}'\\\",\\\"locale\\\":\\\"'${TTS_LOCALE}'\\\",\\\"value\\\":\\\"'${SVOL}'\\\"}},{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\\\"type\\\":\\\"'${SEQUENCECMD}'\\\",\\\"operationPayload\\\":{\\\"deviceType\\\":\\\"'${DEVICETYPE}'\\\",\\\"deviceSerialNumber\\\":\\\"'${DEVICESERIALNUMBER}'\\\",\\\"customerId\\\":\\\"'${MEDIAOWNERCUSTOMERID}'\\\",\\\"locale\\\":\\\"'${TTS_LOCALE}'\\\"'${SEQUENCEVAL}'}},{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\\\"type\\\":\\\"Alexa.DeviceControls.Volume\\\",\\\"operationPayload\\\":{\\\"deviceType\\\":\\\"'${DEVICETYPE}'\\\",\\\"deviceSerialNumber\\\":\\\"'${DEVICESERIALNUMBER}'\\\",\\\"customerId\\\":\\\"'${MEDIAOWNERCUSTOMERID}'\\\",\\\"locale\\\":\\\"'${TTS_LOCALE}'\\\",\\\"value\\\":\\\"'${VOL}'\\\"}}]}}\",\"status\":\"ENABLED\"}'\n\telse\n\t\tALEXACMD='{\"behaviorId\":\"PREVIEW\",\"sequenceJson\":\"{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.Sequence\\\",\\\"startNode\\\":{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\\\"type\\\":\\\"'${SEQUENCECMD}'\\\",\\\"operationPayload\\\":{\\\"deviceType\\\":\\\"'${DEVICETYPE}'\\\",\\\"deviceSerialNumber\\\":\\\"'${DEVICESERIALNUMBER}'\\\",\\\"customerId\\\":\\\"'${MEDIAOWNERCUSTOMERID}'\\\",\\\"locale\\\":\\\"'${TTS_LOCALE}'\\\"'${SEQUENCEVAL}'}}}\",\"status\":\"ENABLED\"}'\n\tfi\n\n\t# Due to some weird shell-escape-behavior the command has to be written to a file before POSTing it\n\techo $ALEXACMD > \"${TMP}/.alexa.cmd\"\n\n\t${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d @\"${TMP}/.alexa.cmd\"\\\n\t \"https://${ALEXA}/api/behaviors/preview\"\n\n\trm -f \"${TMP}/.alexa.cmd\"\n\t\t \nelse\n\t${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n\t -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n\t -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d ${COMMAND}\\\n\t \"https://${ALEXA}/api/np/command?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\"\nfi\n}\n\n#\n# play TuneIn radio station\n#\nplay_radio()\n{\n JSON='{\"contentToken\":\"music:'$(echo '[\"music/tuneIn/stationId\",\"'${STATIONID}'\"]|{\"previousPageId\":\"TuneIn_SEARCH\"}'| base64 -w 0| base64 -w 0 )'\"}'\n\n ${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X PUT -d \"${JSON}\" \\\n \"https://${ALEXA}/api/entertainment/v1/player/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\"\n}\n\n#\n# play library track\n#\nplay_song()\n{\n\tif [ -z \"${ALBUM}\" ] ; then\n\t\tJSON=\"{\\\"trackId\\\":\\\"${SONG}\\\",\\\"playQueuePrime\\\":true}\"\n\telse\n\t\tJSON=\"{\\\"albumArtistName\\\":\\\"${ARTIST}\\\",\\\"albumName\\\":\\\"${ALBUM}\\\"}\"\n\tfi\n\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"${JSON}\"\\\n \"https://${ALEXA}/api/cloudplayer/queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}&shuffle=false\"\n}\n\n#\n# play library playlist\n#\nplay_playlist()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"playlistId\\\":\\\"${PLIST}\\\",\\\"playQueuePrime\\\":true}\"\\\n \"https://${ALEXA}/api/cloudplayer/queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}&shuffle=false\"\n}\n\n#\n# play PRIME playlist\n#\nplay_prime_playlist()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"asin\\\":\\\"${ASIN}\\\"}\"\\\n \"https://${ALEXA}/api/prime/prime-playlist-queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}\"\n}\n\n#\n# play PRIME station\n#\nplay_prime_station()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"seed\\\":\\\"{\\\\\\\"type\\\\\\\":\\\\\\\"KEY\\\\\\\",\\\\\\\"seedId\\\\\\\":\\\\\\\"${SEEDID}\\\\\\\"}\\\",\\\"stationName\\\":\\\"none\\\",\\\"seedType\\\":\\\"KEY\\\"}\"\\\n \"https://${ALEXA}/api/gotham/queue-and-play?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}&mediaOwnerCustomerId=${MEDIAOWNERCUSTOMERID}\"\n}\n\n#\n# play PRIME historical queue\n#\nplay_prime_hist_queue()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"deviceType\\\":\\\"${DEVICETYPE}\\\",\\\"deviceSerialNumber\\\":\\\"${DEVICESERIALNUMBER}\\\",\\\"mediaOwnerCustomerId\\\":\\\"${MEDIAOWNERCUSTOMERID}\\\",\\\"queueId\\\":\\\"${HIST}\\\",\\\"service\\\":null,\\\"trackSource\\\":\\\"TRACK\\\"}\"\\\n \"https://${ALEXA}/api/media/play-historical-queue\"\n}\n\n#\n# current queue\n#\nshow_queue()\n{\n\techo \"/api/np/player\"\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/np/player?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\"\n\techo\n\techo \"/api/np/queue\"\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/np/queue?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\"\n\techo\n\techo \"/api/media/state\"\n ${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/media/state?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\"\n\techo\n}\n\n#\n# show notifications and alarms\n#\nshow_notifications()\n{\n\techo \"/api/notifications\"\n ${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/notifications?deviceSerialNumber=${DEVICESERIALNUMBER}&deviceType=${DEVICETYPE}\"\n\techo\n}\n\n#\n# deletes a multiroom device\n#\ndelete_multiroom()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X DELETE \\\n \"https://${ALEXA}/api/lemur/tail/${DEVICESERIALNUMBER}\"\n}\n\n#\n# creates a multiroom device\n#\ncreate_multiroom()\n{\n\tJSON=\"{\\\"id\\\":null,\\\"name\\\":\\\"${LEMUR}\\\",\\\"members\\\":[\"\n\tfor DEVICE in $CHILD ; do\n\t\tset_var\n\t\tJSON=\"${JSON}{\\\"dsn\\\":\\\"${DEVICESERIALNUMBER}\\\",\\\"deviceType\\\":\\\"${DEVICETYPE}\\\"},\"\n\tdone\n\tJSON=\"${JSON%,}]}\"\n\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"${JSON}\" \\\n \"https://${ALEXA}/api/lemur/tail\"\n}\n\n#\n# list bluetooth devices\n#\nlist_bluetooth()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/bluetooth?cached=false\"\n}\n\n#\n# connect bluetooth device\n#\nconnect_bluetooth()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST -d \"{\\\"bluetoothDeviceAddress\\\":\\\"${BLUETOOTH}\\\"}\"\\\n \"https://${ALEXA}/api/bluetooth/pair-sink/${DEVICETYPE}/${DEVICESERIALNUMBER}\"\n}\n\n#\n# disconnect bluetooth device\n#\ndisconnect_bluetooth()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X POST \\\n \"https://${ALEXA}/api/bluetooth/disconnect-sink/${DEVICETYPE}/${DEVICESERIALNUMBER}\"\n}\n\n#\n# device that sent the last command\n# (by Markus Wennesheimer)\n#\nlast_alexa()\n{\n${CURL} ${OPTS} -s -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n -H \"Content-Type: application/json; charset=UTF-8\" -H \"Referer: https://alexa.${AMAZON}/spa/index.html\" -H \"Origin: https://alexa.${AMAZON}\"\\\n -H \"csrf: $(awk \"\\$0 ~/.${AMAZON}.*csrf[ \\\\s\\\\t]+/ {print \\$7}\" ${COOKIE})\" -X GET \\\n \"https://${ALEXA}/api/activities?startTime=&size=1&offset=1\" | sed -r 's/^.*serialNumber\":\"([^\"]+)\".*$/\\1/'\n }\n\n#\n# logout\n#\nlog_off()\n{\n${CURL} ${OPTS} -s -c ${COOKIE} -b ${COOKIE} -A \"${BROWSER}\" -H \"DNT: 1\" -H \"Connection: keep-alive\" -L\\\n https://${ALEXA}/logout > /dev/null\n\nrm -f ${DEVLIST}\nrm -f ${DEVTXT}\nrm -f ${DEVALL}\nrm -f ${COOKIE}\n}\n\nif [ -z \"$LASTALEXA\" -a -z \"$BLUETOOTH\" -a -z \"$LEMUR\" -a -z \"$PLIST\" -a -z \"$HIST\" -a -z \"$SEEDID\" -a -z \"$ASIN\" -a -z \"$QUEUE\" -a -z \"$NOTIFICATIONS\" -a -z \"$COMMAND\" -a -z \"$STATIONID\" -a -z \"$SONG\" -a -n \"$LOGOFF\" ] ; then\n\techo \"only logout option present, logging off ...\"\n\tlog_off\n\texit 0\nfi\n\nif [ ! -f ${COOKIE} ] ; then\n\techo \"cookie does not exist. logging in ...\"\n\tlog_in\nfi\n\ncheck_status\nif [ $? -eq 0 ] ; then\n\techo \"cookie expired, logging in again ...\"\n\tlog_in\n\tcheck_status\n\tif [ $? -eq 0 ] ; then\n\t\techo \"log in failed, aborting\"\n\t\texit 1\n\tfi\nfi\n\nif [ ! -f ${DEVTXT} -o ! -f ${DEVALL} ] ; then\n\techo \"device list does not exist. downloading ...\"\n\tget_devlist\n\tif [ ! -f ${DEVTXT} ] ; then\n\t\techo \"failed to download device list, aborting\"\n\t\texit 1\n\tfi\nfi\n\nif [ -n \"$LOGIN\" ] ; then\n\techo \"logged in\"\n\texit 0\nfi\n\nif [ -n \"$COMMAND\" -o -n \"$QUEUE\" -o -n \"$NOTIFICATIONS\" ] ; then\n\tif [ \"${DEVICE}\" = \"ALL\" ] ; then\n\t\twhile IFS= read -r DEVICE ; do\n\t\t\tset_var\n\t\t\tif [ \"$DEVICESTATE\" = \"true\" ] ; then\n\t\t\t\tif [ -n \"$COMMAND\" ] ; then\n\t\t\t\t\techo \"sending cmd:${COMMAND} to dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} customerid:${MEDIAOWNERCUSTOMERID}\"\n\t\t\t\t\trun_cmd\n\t\t\t\t\t# in order to prevent a \"Rate exceeded\" we need to delay the command\n\t\t\t\t\tsleep 1\n\t\t\t\t\techo\n\t\t\t\telif [ -n \"$NOTIFICATIONS\" ] ; then\n\t\t\t\t\techo \"notifications info for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\t\t\tshow_notifications\n\t\t\t\telse\n\t\t\t\t\techo \"queue info for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\t\t\tshow_queue\n\t\t\t\t\techo\n\t\t\t\tfi\n\t\t\tfi\n\t\tdone < ${DEVALL}\n\telse\n\t\tset_var\n\t\tif [ -n \"$COMMAND\" ] ; then\n\t\t\techo \"sending cmd:${COMMAND} to dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} customerid:${MEDIAOWNERCUSTOMERID}\"\n\t\t\trun_cmd\n\t\t\techo\n\t\telif [ -n \"$NOTIFICATIONS\" ] ; then\n\t\t\techo \"notifications info for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\tshow_notifications\n\t\telse\n\t\t\techo \"queue info for dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER}\"\n\t\t\tshow_queue\n\t\t\techo\n\t\tfi\n\tfi\nelif [ -n \"$LEMUR\" ] ; then\n\tDEVICE=\"${LEMUR}\"\n\tset_var\n\tif [ -n \"$DEVICESERIALNUMBER\" ] ; then\n\t\tdelete_multiroom\n\tfi\n\tif [ -z \"$CHILD\" ] ; then\n\t\techo \"Deleted multi room dev:${LEMUR} serial:${DEVICESERIALNUMBER}\"\n\telse\n\t\techo \"Creating multi room dev:${LEMUR} member_dev(s):${CHILD}\"\n\t\tcreate_multiroom\n\t\techo\n\tfi\n\trm -f ${DEVLIST}\n\trm -f ${DEVALL}\n\trm -f ${DEVTXT}\n\tget_devlist\nelif [ -n \"$BLUETOOTH\" ] ; then\n\tif [ \"$BLUETOOTH\" = \"list\" -o \"$BLUETOOTH\" = \"List\" -o \"$BLUETOOTH\" = \"LIST\" ] ; then\n\t\tif [ \"${DEVICE}\" = \"ALL\" ] ; then\n\t\t\twhile IFS= read -r DEVICE ; do\n\t\t\t\tset_var\n\t\t\t\techo \"bluetooth api list:\"\n\t\t\t\tlist_bluetooth\n\t\t\t\techo\n\t\t\tdone < ${DEVALL}\n\t\telse\n\t\t\tset_var\n\t\t\techo \"bluetooth api list:\"\n\t\t\tlist_bluetooth\n\t\t\techo\n\t\tfi\n\telif [ \"$BLUETOOTH\" = \"null\" ] ; then\n\t\tset_var\n\t\techo \"disconnecting dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} from bluetooth\"\n\t\tdisconnect_bluetooth\n\t\techo\n\telse\n\t\tset_var\n\t\techo \"connecting dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} to bluetooth device:${BLUETOOTH}\"\n\t\tconnect_bluetooth\n\t\techo\n\tfi\nelif [ -n \"$STATIONID\" ] ; then\n\tset_var\n\techo \"playing stationID:${STATIONID} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} mediaownerid:${MEDIAOWNERCUSTOMERID}\"\n\tplay_radio\nelif [ -n \"$SONG\" ] ; then\n\tset_var\n\techo \"playing library track:${SONG} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} mediaownerid:${MEDIAOWNERCUSTOMERID}\"\n\tplay_song\nelif [ -n \"$PLIST\" ] ; then\n\tset_var\n\techo \"playing library playlist:${PLIST} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} mediaownerid:${MEDIAOWNERCUSTOMERID}\"\n\tplay_playlist\nelif [ -n \"$ASIN\" ] ; then\n\tset_var\n\techo \"playing PRIME playlist ${ASIN}\"\n\tplay_prime_playlist\nelif [ -n \"$SEEDID\" ] ; then\n\tset_var\n\techo \"playing PRIME station ${SEEDID}\"\n\tplay_prime_station\nelif [ -n \"$HIST\" ] ; then\n\tset_var\n\techo \"playing PRIME historical queue ${HIST}\"\n\tplay_prime_hist_queue\nelif [ -n \"$LIST\" ] ; then\n\tATTR=\"accountName\"\n\techo \"the following devices exist in your account:\"\n\tgrep ${ATTR}\\| ${DEVTXT} | sed \"s/^.*${ATTR}|//\" | sed 's/ /_/g'\nelif [ -n \"$LASTALEXA\" ] ; then\n\tlast_alexa\nelse\n\techo \"no alexa command received\"\nfi\n\nif [ -n \"$LOGOFF\" ] ; then\n\techo \"logout option present, logging off ...\"\n\tlog_off\nfi\n"
  }
]