platypush/platypush/backend/pushbullet/__init__.py

225 lines
6.9 KiB
Python

import asyncio
import json
import requests
import time
import websockets
from platypush.config import Config
from platypush.context import get_or_create_event_loop
from platypush.message import Message
from platypush.message.event.pushbullet import PushbulletEvent
from .. import Backend
class PushbulletBackend(Backend):
"""
This backend will listen for events on a Pushbullet (https://pushbullet.com)
channel and propagate them to the bus. This backend is quite useful if you
want to synchronize events and actions with your mobile phone (through the
Pushbullet app and/or through Tasker), synchronize clipboards, send pictures
and files to other devices etc. You can also wrap Platypush messages as JSON
into a push body to execute them.
Triggers:
* :class:`platypush.message.event.pushbullet.PushbulletEvent` if a new push is received
Requires:
* **requests** (``pip install requests``)
* **websockets** (``pip install websockets``)
"""
def __init__(self, token, device='Platypush', **kwargs):
"""
:param token: Your Pushbullet API token, see https://docs.pushbullet.com/#authentication
:type token: str
:param device: Name of the virtual device for Platypush (default: Platypush)
:type device: str
"""
super().__init__(**kwargs)
self.token = token
self.device_name = device
self.pb_device_id = self.get_device_id()
self.ws = None
self._last_received_msg = {
'request' : { 'body': None, 'time': None },
'response' : { 'body': None, 'time': None },
'event' : { 'body': None, 'time': None },
}
def _get_latest_push(self):
t = int(time.time()) - 5
try:
response = requests.get(
u'https://api.pushbullet.com/v2/pushes',
headers = { 'Access-Token': self.token },
params = {
'modified_after': str(t),
'active' : 'true',
'limit' : 1,
}
)
response = response.json()
except Exception as e:
self.logger.exception(e)
raise e
if 'pushes' in response and response['pushes']:
return response['pushes'][0]
else:
return {}
def _should_skip_last_received_msg(self, msg):
if not isinstance(msg, dict): return True # We received something weird
is_duplicate=False
last_msg = self._last_received_msg[msg['type']]
if last_msg:
msg = Message.parse(msg)
if str(msg) == str(last_msg['body']) \
and time.time() - last_msg['time'] <= 2:
# Duplicate message sent on the Pushbullet socket within
# two seconds, ignore it
self.logger.debug('Ignoring duplicate message received on the socket')
is_duplicate = True
self._last_received_msg[msg['type']] = {
'body': msg, 'time': time.time()
}
return is_duplicate
def on_push(self, ws, data):
try:
# Parse the push
try:
data = json.loads(data) if isinstance(data, str) else data
except Exception as e:
self.logger.exception(e)
return
# If it's a push, get it
if data['type'] == 'tickle' and data['subtype'] == 'push':
push = self._get_latest_push()
elif data['type'] == 'push':
push = data['push']
else: return # Not a push notification
# Post an event, useful to react on mobile notifications if
# you enabled notification mirroring on your PushBullet app
event = PushbulletEvent(**push)
self.on_message(event)
if 'body' not in push: return
self.logger.debug('Received push: {}'.format(push))
body = push['body']
try: body = json.loads(body)
except ValueError as e: return # Some other non-JSON push
if not self._should_skip_last_received_msg(body):
self.on_message(body)
except Exception as e:
self.logger.exception(e)
return
def on_error(self, ws, e):
self.logger.exception(e)
self.logger.info('Restarting PushBullet backend')
ws.close()
self._init_socket()
def _init_socket(self):
async def pushbullet_client():
async with websockets.connect('wss://stream.pushbullet.com/websocket/'
+ self.token) as self.ws:
while True:
try:
push = await self.ws.recv()
except Exception as e:
self.on_error(ws, e)
break
self.on_push(self.ws, push)
self.close()
loop = get_or_create_event_loop()
loop.run_until_complete(pushbullet_client())
loop.run_forever()
def create_device(self, name):
return requests.post(
u'https://api.pushbullet.com/v2/devices',
headers = { 'Access-Token': self.token },
json = {
'nickname': name,
'model': 'Platypush virtual device',
'manufactorer': 'platypush',
'app_version': 8623,
'icon': 'system',
}
).json()
def get_device_id(self):
response = requests.get(
u'https://api.pushbullet.com/v2/devices',
headers = { 'Access-Token': self.token },
).json()
devices = [dev for dev in response['devices'] if 'nickname' in dev
and dev['nickname'] == self.device_name]
if devices:
return devices[0]['iden']
try:
response = self.create_device(self.device_name)
if 'iden' not in response:
raise RuntimeError()
self.logger.info('Created Pushbullet device {}'.format(
self.device_name))
return response['iden']
except Exception as e:
self.logger.error('Unable to create Pushbillet device {}'.
format(self.device_name))
def send_message(self, msg):
requests.post(
u'https://api.pushbullet.com/v2/pushes',
headers = { 'Access-Token': self.token },
json = {
'type': 'note',
'device_iden': self.pb_device_id,
'body': str(msg)
}
).json()
def close(self):
if self.ws:
self.ws.close()
def on_stop(self):
return self.close()
def run(self):
super().run()
self.logger.info('Initialized Pushbullet backend - device_id: {}'
.format(self.device_name))
self._init_socket()
# vim:sw=4:ts=4:et: