diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 4b87a7c..2cd74e4 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.2.0a1 +current_version = 1.2.0 commit = True tag = True diff --git a/HISTORY.rst b/HISTORY.rst index 5113b8c..ce6c1b8 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,14 @@ History ======= +v1.3.0 (2018-09-23) +------------------- + +- Pushbullet notifications +- Home Assistant ``access_token`` option +- Amazon-dash v2.0.0 disclaimer + + v1.2.0 (2018-09-03) ------------------- diff --git a/README.rst b/README.rst index 48229aa..7a11964 100644 --- a/README.rst +++ b/README.rst @@ -93,6 +93,7 @@ Also available on `AUR `_. openhab: 192.168.1.140 item: open_door state: "ON" + confirmation: send-pb 44:65:0D:75:A7:B2: # IFTTT example name: Pompadour ifttt: cdxxx-_gEJ3wdU04yyyzzz @@ -104,6 +105,10 @@ Also available on `AUR `_. token: '402642618:QwGDgiKE3LqdkNAtBkq0UEeBoDdpZYw8b4h' to: 24291592 is_default: false + send-pb: + service: pushbullet + token: 'o.BbbPYjJizbPr2gSWgXGmqNTt6T9Rew51' + is_default: false **UPGRADE** from `previous versions `_ @@ -119,7 +124,7 @@ The following execution methods are supported with your Amazon Dash button with Amazon-dash also allows you to **send a confirmation** after pressing a button. You will also receive a message in -case of failure. Currently only **Telegram** is supported. +case of failure. **Telegram** and **Pushbullet** are supported. For more information see diff --git a/amazon-dash.png b/amazon-dash.png index 0463ef9..f661b0a 100644 Binary files a/amazon-dash.png and b/amazon-dash.png differ diff --git a/amazon_dash/__init__.py b/amazon_dash/__init__.py index 12e56b6..7395f21 100644 --- a/amazon_dash/__init__.py +++ b/amazon_dash/__init__.py @@ -1,2 +1,2 @@ -__version__ = '1.2.0a1' \ No newline at end of file +__version__ = '1.2.0' \ No newline at end of file diff --git a/amazon_dash/config.py b/amazon_dash/config.py index a099508..c981662 100644 --- a/amazon_dash/config.py +++ b/amazon_dash/config.py @@ -106,7 +106,10 @@ "type": "object", "properties": { "service": { - "enum": ['telegram'] + "enum": [ + 'telegram', + 'pushbullet', + ] }, "token": { "type": "string", diff --git a/amazon_dash/confirmations.py b/amazon_dash/confirmations.py index 48c1f70..7a90800 100644 --- a/amazon_dash/confirmations.py +++ b/amazon_dash/confirmations.py @@ -47,8 +47,48 @@ def send(self, message, success=True): )) +class PushbulletConfirmation(ConfirmationBase): + url_base = 'https://api.pushbullet.com/v2/pushes' + name = 'pushbullet' + required_fields = ('token',) + one_field_of = {'device_iden', 'email', 'channel_tag', 'client_id'} + to_field = None + + def __init__(self, data): + one_fields = set(data) & self.one_field_of + if len(one_fields) > 1: + raise InvalidConfig(extra_body='Only one in {} is required for {} notifications'.format( + ', '.join(one_fields), self.name)) + elif one_fields: + self.to_field = one_fields.pop() + super(PushbulletConfirmation, self).__init__(data) + + def get_data(self, body, title=''): + data = { + "type": "note", + "body": body, + } + if title: + data["title"] = title + if self.to_field: + data[self.to_field] = self.data[self.to_field] + return data + + def send(self, message, success=True): + try: + r = requests.post(self.url_base, json=self.get_data(message), + headers={'Access-Token': self.data['token']}) + except RequestException as e: + raise ConfirmationError('Unable to connect to Pushbullet servers on pushbullet confirmation: {}'.format(e)) + try: + r.json() + except JSONDecodeError: + raise ConfirmationError('Invalid JSON response in pushbullet confirmation (server error?)') + + CONFIRMATIONS = { 'telegram': TelegramConfirmation, + 'pushbullet': PushbulletConfirmation, 'disabled': DisabledConfirmation, } diff --git a/amazon_dash/execute.py b/amazon_dash/execute.py index dc130a0..48c9d17 100644 --- a/amazon_dash/execute.py +++ b/amazon_dash/execute.py @@ -341,9 +341,13 @@ def get_url(self): return url def get_headers(self): - return { - 'x-ha-access': self.data['access'] - } if 'access' in self.data else {} + headers = {} + if 'access_token' in self.data: + headers['Authorization'] = 'Bearer {0}'.format(self.data['access_token']) + elif 'access' in self.data: + headers['x-ha-access'] = self.data['access'] + + return headers class ExecuteOpenHab(ExecuteOwnApiBase): diff --git a/amazon_dash/install/amazon-dash.yml b/amazon_dash/install/amazon-dash.yml index c916349..e701fc4 100644 --- a/amazon_dash/install/amazon-dash.yml +++ b/amazon_dash/install/amazon-dash.yml @@ -53,6 +53,10 @@ devices: # token: '402642618:QwGDgiKE3LqdkNAtBkq0UEeBoDdpZYw8b4h' # Telegram token. Get it from Bothfather # to: 24291592 # Your Telegram id. You can get it using @get_id_bot # is_default: true # Use by default this confirmation for all devices +# send-pb: +# service: pushbullet +# token: 'o.BbbPYjJizbPr2gSWgXGmqNTt6T9Rew51' +# is_default: false # Need help? See the documentation: # http://docs.nekmo.org/amazon-dash/config_file.html diff --git a/amazon_dash/tests/test_confirmations.py b/amazon_dash/tests/test_confirmations.py index 8c16690..5e69919 100644 --- a/amazon_dash/tests/test_confirmations.py +++ b/amazon_dash/tests/test_confirmations.py @@ -4,7 +4,7 @@ import requests_mock from amazon_dash.confirmations import get_confirmation, DisabledConfirmation, get_confirmation_instance, \ - TelegramConfirmation, ConfirmationBase + TelegramConfirmation, ConfirmationBase, PushbulletConfirmation from amazon_dash.exceptions import InvalidConfig, ConfirmationError @@ -71,3 +71,40 @@ def test_server_error(self): m.post(telegram.url_base.format('foo'), exc=requests.exceptions.ConnectTimeout) with self.assertRaises(ConfirmationError): telegram.send('spam') + + +class TestPushbulletConfirmation(unittest.TestCase): + def get_pushbullet(self, extra=None): + extra = extra or {} + return PushbulletConfirmation(dict({'token': 'foo'}, **extra)) + + def test_send(self): + pushbullet = self.get_pushbullet() + with requests_mock.mock() as m: + m.post(pushbullet.url_base, text='{}') + pushbullet.send('spam') + self.assertTrue(m.called_once) + + def test_invalid_json(self): + pushbullet = self.get_pushbullet() + with requests_mock.mock() as m: + m.post(pushbullet.url_base, text='{"}invalid') + with self.assertRaises(ConfirmationError): + pushbullet.send('spam') + + def test_server_error(self): + pushbullet = self.get_pushbullet() + with requests_mock.mock() as m: + m.post(pushbullet.url_base, exc=requests.exceptions.ConnectTimeout) + with self.assertRaises(ConfirmationError): + pushbullet.send('spam') + + def test_extra_to(self): + with self.assertRaises(InvalidConfig): + self.get_pushbullet({'device_iden': 'foo', 'email': 'bar'}) + + def test_to_device_iden(self): + pushbullet = self.get_pushbullet({'device_iden': 'bar'}) + with requests_mock.mock() as m: + m.post(pushbullet.url_base, additional_matcher=lambda r: r.json().get('device_iden') == 'bar', text='{}') + pushbullet.send('spam') diff --git a/amazon_dash/tests/test_execute.py b/amazon_dash/tests/test_execute.py index b478f5c..0474fa9 100644 --- a/amazon_dash/tests/test_execute.py +++ b/amazon_dash/tests/test_execute.py @@ -132,7 +132,7 @@ def test_get_shell(self): class TestExecuteUrl(unittest.TestCase): no_body_methods = ['get', 'head', 'delete', 'connect', 'options', 'trace'] url = 'http://domain.com' - + def setUp(self): super(TestExecuteUrl, self).setUp() self.session_mock = requests_mock.Mocker() @@ -297,6 +297,13 @@ def test_execute_with_access(self): assis.execute() self.assertTrue(m.called_once) + def test_execute_with_access_token(self): + with requests_mock.mock() as m: + m.post(self.url, text='success', request_headers={'Authorization': 'Bearer abcde12345'}) + assis = ExecuteHomeAssistant('key', self.default_data(extra_data={'access_token': 'abcde12345'})) + assis.execute() + self.assertTrue(m.called_once) + class TestExecuteOpenHab(unittest.TestCase): path = '/rest/items/test' diff --git a/docs/config_file.rst b/docs/config_file.rst index eaa68de..0d82cad 100644 --- a/docs/config_file.rst +++ b/docs/config_file.rst @@ -61,6 +61,7 @@ Real example: openhab: 192.168.1.140 item: open_door state: "ON" + confirmation: send-pb 44:65:0D:75:A7:B2: name: Pompadour ifttt: cdxxx-_gEJ3wdU04yyyzzz @@ -72,6 +73,10 @@ Real example: token: '402642618:QwGDgiKE3LqdkNAtBkq0UEeBoDdpZYw8b4h' to: 24291592 is_default: false + send-pb: + service: pushbullet + token: 'o.BbbPYjJizbPr2gSWgXGmqNTt6T9Rew51' + is_default: false Common options @@ -98,7 +103,7 @@ for each device. The available exection methods are: * **cmd**: local command line command. Arguments can be placed after the command. * **url**: Call a url. -* **homeassistant**: send event to Homeassistant. This argument must be the address to the hass server (protocol and +* **homeassistant**: send event to Home Assistant. This argument must be the address to the hass server (protocol and port are optional. By default http and 8123, respectively). * **openhab**: send event to OpenHAB. This argument must be the address to the hass server (protocol and port are optional. By default http and 8080, respectively). @@ -248,13 +253,21 @@ Example: confirmation: send-tg -Homeassistant event -~~~~~~~~~~~~~~~~~~~ +Home Assistant event +~~~~~~~~~~~~~~~~~~~~ When the **homeassistant execution method** is used, the following options are available. * **event** (required): Event name to send. * **data**: Event data to send. Use json as string. -* **access**: HomeAssistant password for API (``x-ha-access`` header). +* **access_token**: Long-lived Home Assistant access token. +* **access**: Home Assistant legacy API password (``x-ha-access`` header). + +Starting with version 0.78 of Home Assistant, there are two ways Amazon Dash can authenticate: + +1. By providing a long-lived access token (generated within your Home Assistant profile page) via the ``access_token`` option. +2. By providing the legacy Home Assistant API password via the ``access`` option. + +Although both options currently work, the Home Assistant project plans to deprecate (and likely remove) the legacy API password in the future; therefore, to properly future proof your Amazon Dash setup, the long-lived access token option is recommended. The protocol and the port in the address of the Homeassistant server are optional. The syntax of the address is: ``[://][:]. For example: ``https://hassio.local:1234``. @@ -337,6 +350,7 @@ Confirmations ------------- The following **services** are supported to send confirmation messages. + Telegram ~~~~~~~~ For use a telegram service you need to define: @@ -361,3 +375,32 @@ have not started a conversation before. token: '402642618:QwGDgiKE3LqdkNAtBkq0UEeBoDdpZYw8b4h' to: 24291592 is_default: false + + +Pushbullet +~~~~~~~~~~ +For use a pushbullet service you need to define: + +* **token**: Get it in your pushbullet Access Token (create a token): https://www.pushbullet.com/#settings/account + +Optional: set a target (you can only set a target): + +* **device_iden**: Device identifier. To get your device identifier: + ``$ curl --header 'Access-Token: ' https://api.pushbullet.com/v2/devices`` +* **email**: Useful to send a message to your contacts. +* **channel_tag**: Send to all subscribers to your channel. +* **client_iden**: Send to all users who have granted access to your OAuth client. + +.. code-block:: yaml + + # amazon-dash.yml + # --------------- + settings: + delay: 10 + devices: + # ... + confirmations: + send-pb: + service: pushbullet + token: 'o.BbbPYjJizbPr2gSWgXGmqNTt6T9Rew51' + is_default: false diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 89fd95c..8477981 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -11,6 +11,8 @@ are: * Tcpdump. * Sudo +Amazon-dash v2.0.0 will only be compatible with Python 3.6+. This version is currently in development. + Why root is required -------------------- diff --git a/images/amazon-dash.xcf b/images/amazon-dash.xcf index 8db373e..8402bd5 100644 Binary files a/images/amazon-dash.xcf and b/images/amazon-dash.xcf differ