forked from platypush/platypush
Blackened the qrcode and pushbullet plugins
This commit is contained in:
parent
aaac6488d6
commit
7eca1c27c9
2 changed files with 144 additions and 80 deletions
|
@ -16,7 +16,8 @@ class PushbulletPlugin(Plugin):
|
||||||
|
|
||||||
Requires:
|
Requires:
|
||||||
|
|
||||||
* The :class:`platypush.backend.pushbullet.Pushbullet` backend enabled
|
* The :class:`platypush.backend.pushbullet.PushbulletBackend` backend enabled
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, token: Optional[str] = None, **kwargs):
|
def __init__(self, token: Optional[str] = None, **kwargs):
|
||||||
|
@ -44,23 +45,23 @@ class PushbulletPlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
Get the list of available devices
|
Get the list of available devices
|
||||||
"""
|
"""
|
||||||
resp = requests.get('https://api.pushbullet.com/v2/devices',
|
resp = requests.get(
|
||||||
headers={'Authorization': 'Bearer ' + self.token,
|
'https://api.pushbullet.com/v2/devices',
|
||||||
'Content-Type': 'application/json'})
|
headers={
|
||||||
|
'Authorization': 'Bearer ' + self.token,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
self._devices = resp.json().get('devices', [])
|
self._devices = resp.json().get('devices', [])
|
||||||
self._devices_by_id = {
|
self._devices_by_id = {dev['iden']: dev for dev in self._devices}
|
||||||
dev['iden']: dev
|
|
||||||
for dev in self._devices
|
|
||||||
}
|
|
||||||
|
|
||||||
self._devices_by_name = {
|
self._devices_by_name = {
|
||||||
dev['nickname']: dev
|
dev['nickname']: dev for dev in self._devices if 'nickname' in dev
|
||||||
for dev in self._devices if 'nickname' in dev
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_device(self, device):
|
def get_device(self, device) -> Optional[dict]:
|
||||||
"""
|
"""
|
||||||
:param device: Device ID or name
|
:param device: Device ID or name
|
||||||
"""
|
"""
|
||||||
|
@ -79,7 +80,14 @@ class PushbulletPlugin(Plugin):
|
||||||
refreshed = True
|
refreshed = True
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def send_note(self, device: str = None, body: str = None, title: str = None, url: str = None, **kwargs):
|
def send_note(
|
||||||
|
self,
|
||||||
|
device: Optional[str] = None,
|
||||||
|
body: Optional[str] = None,
|
||||||
|
title: Optional[str] = None,
|
||||||
|
url: Optional[str] = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Send a note push.
|
Send a note push.
|
||||||
|
|
||||||
|
@ -90,10 +98,11 @@ class PushbulletPlugin(Plugin):
|
||||||
:param kwargs: Push arguments, see https://docs.pushbullet.com/#create-push
|
:param kwargs: Push arguments, see https://docs.pushbullet.com/#create-push
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
dev = None
|
||||||
if device:
|
if device:
|
||||||
device = self.get_device(device).output
|
dev = self.get_device(device).output
|
||||||
if not device:
|
if not dev:
|
||||||
raise RuntimeError('No such device')
|
raise RuntimeError(f'No such device: {device}')
|
||||||
|
|
||||||
kwargs['body'] = body
|
kwargs['body'] = body
|
||||||
kwargs['title'] = title
|
kwargs['title'] = title
|
||||||
|
@ -102,21 +111,25 @@ class PushbulletPlugin(Plugin):
|
||||||
kwargs['type'] = 'link'
|
kwargs['type'] = 'link'
|
||||||
kwargs['url'] = url
|
kwargs['url'] = url
|
||||||
|
|
||||||
if device:
|
if dev:
|
||||||
# noinspection PyTypeChecker
|
kwargs['device_iden'] = dev['iden']
|
||||||
kwargs['device_iden'] = device['iden']
|
|
||||||
|
|
||||||
resp = requests.post('https://api.pushbullet.com/v2/pushes',
|
resp = requests.post(
|
||||||
data=json.dumps(kwargs),
|
'https://api.pushbullet.com/v2/pushes',
|
||||||
headers={'Authorization': 'Bearer ' + self.token,
|
data=json.dumps(kwargs),
|
||||||
'Content-Type': 'application/json'})
|
headers={
|
||||||
|
'Authorization': 'Bearer ' + self.token,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
raise Exception('Pushbullet push failed with status {}: {}'.
|
raise Exception(
|
||||||
format(resp.status_code, resp.json()))
|
f'Pushbullet push failed with status {resp.status_code}: {resp.json()}'
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def send_file(self, filename: str, device: str = None):
|
def send_file(self, filename: str, device: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
Send a file.
|
Send a file.
|
||||||
|
|
||||||
|
@ -124,48 +137,61 @@ class PushbulletPlugin(Plugin):
|
||||||
:param filename: Path to the local file
|
:param filename: Path to the local file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
dev = None
|
||||||
if device:
|
if device:
|
||||||
device = self.get_device(device).output
|
dev = self.get_device(device).output
|
||||||
if not device:
|
if not dev:
|
||||||
raise RuntimeError('No such device')
|
raise RuntimeError(f'No such device: {device}')
|
||||||
|
|
||||||
resp = requests.post('https://api.pushbullet.com/v2/upload-request',
|
resp = requests.post(
|
||||||
data=json.dumps({'file_name': os.path.basename(filename)}),
|
'https://api.pushbullet.com/v2/upload-request',
|
||||||
headers={'Authorization': 'Bearer ' + self.token,
|
data=json.dumps({'file_name': os.path.basename(filename)}),
|
||||||
'Content-Type': 'application/json'})
|
headers={
|
||||||
|
'Authorization': 'Bearer ' + self.token,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
raise Exception('Pushbullet file upload request failed with status {}'.
|
raise Exception(
|
||||||
format(resp.status_code))
|
f'Pushbullet file upload request failed with status {resp.status_code}'
|
||||||
|
)
|
||||||
|
|
||||||
r = resp.json()
|
r = resp.json()
|
||||||
resp = requests.post(r['upload_url'], data=r['data'],
|
with open(filename, 'rb') as f:
|
||||||
files={'file': open(filename, 'rb')})
|
resp = requests.post(r['upload_url'], data=r['data'], files={'file': f})
|
||||||
|
|
||||||
if resp.status_code != 204:
|
if resp.status_code != 204:
|
||||||
raise Exception('Pushbullet file upload failed with status {}'.
|
raise Exception(
|
||||||
format(resp.status_code))
|
f'Pushbullet file upload failed with status {resp.status_code}'
|
||||||
|
)
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
resp = requests.post(
|
||||||
resp = requests.post('https://api.pushbullet.com/v2/pushes',
|
'https://api.pushbullet.com/v2/pushes',
|
||||||
headers={'Authorization': 'Bearer ' + self.token,
|
headers={
|
||||||
'Content-Type': 'application/json'},
|
'Authorization': 'Bearer ' + self.token,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
data=json.dumps({
|
},
|
||||||
'type': 'file',
|
data=json.dumps(
|
||||||
'device_iden': device['iden'] if device else None,
|
{
|
||||||
'file_name': r['file_name'],
|
'type': 'file',
|
||||||
'file_type': r['file_type'],
|
'device_iden': dev['iden'] if dev else None,
|
||||||
'file_url': r['file_url']}))
|
'file_name': r['file_name'],
|
||||||
|
'file_type': r['file_type'],
|
||||||
|
'file_url': r['file_url'],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
raise Exception('Pushbullet file push failed with status {}'.
|
raise Exception(
|
||||||
format(resp.status_code))
|
f'Pushbullet file push failed with status {resp.status_code}'
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'filename': r['file_name'],
|
'filename': r['file_name'],
|
||||||
'type': r['file_type'],
|
'type': r['file_type'],
|
||||||
'url': r['file_url']
|
'url': r['file_url'],
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -178,17 +204,23 @@ class PushbulletPlugin(Plugin):
|
||||||
backend = get_backend('pushbullet')
|
backend = get_backend('pushbullet')
|
||||||
device_id = backend.get_device_id() if backend else None
|
device_id = backend.get_device_id() if backend else None
|
||||||
|
|
||||||
resp = requests.post('https://api.pushbullet.com/v2/ephemerals',
|
resp = requests.post(
|
||||||
data=json.dumps({
|
'https://api.pushbullet.com/v2/ephemerals',
|
||||||
'type': 'push',
|
data=json.dumps(
|
||||||
'push': {
|
{
|
||||||
'body': text,
|
'type': 'push',
|
||||||
'type': 'clip',
|
'push': {
|
||||||
'source_device_iden': device_id,
|
'body': text,
|
||||||
},
|
'type': 'clip',
|
||||||
}),
|
'source_device_iden': device_id,
|
||||||
headers={'Authorization': 'Bearer ' + self.token,
|
},
|
||||||
'Content-Type': 'application/json'})
|
}
|
||||||
|
),
|
||||||
|
headers={
|
||||||
|
'Authorization': 'Bearer ' + self.token,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,11 @@ from typing import Optional, List
|
||||||
from platypush import Config
|
from platypush import Config
|
||||||
from platypush.context import get_bus
|
from platypush.context import get_bus
|
||||||
from platypush.message.event.qrcode import QrcodeScannedEvent
|
from platypush.message.event.qrcode import QrcodeScannedEvent
|
||||||
from platypush.message.response.qrcode import QrcodeGeneratedResponse, QrcodeDecodedResponse, ResultModel
|
from platypush.message.response.qrcode import (
|
||||||
|
QrcodeGeneratedResponse,
|
||||||
|
QrcodeDecodedResponse,
|
||||||
|
ResultModel,
|
||||||
|
)
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
from platypush.plugins.camera import CameraPlugin
|
from platypush.plugins.camera import CameraPlugin
|
||||||
from platypush.utils import get_plugin_class_by_name
|
from platypush.utils import get_plugin_class_by_name
|
||||||
|
@ -36,20 +40,28 @@ class QrcodePlugin(Plugin):
|
||||||
self.camera_plugin = camera_plugin
|
self.camera_plugin = camera_plugin
|
||||||
self._capturing = threading.Event()
|
self._capturing = threading.Event()
|
||||||
|
|
||||||
def _get_camera(self, camera_plugin: Optional[str] = None, **config) -> CameraPlugin:
|
def _get_camera(
|
||||||
|
self, camera_plugin: Optional[str] = None, **config
|
||||||
|
) -> CameraPlugin:
|
||||||
camera_plugin = camera_plugin or self.camera_plugin
|
camera_plugin = camera_plugin or self.camera_plugin
|
||||||
if not config:
|
if not config:
|
||||||
config = Config.get(camera_plugin) or {}
|
config = Config.get(camera_plugin) or {}
|
||||||
config['stream_raw_frames'] = True
|
config['stream_raw_frames'] = True
|
||||||
|
|
||||||
cls = get_plugin_class_by_name(camera_plugin)
|
cls = get_plugin_class_by_name(camera_plugin)
|
||||||
assert cls and issubclass(cls, CameraPlugin), '{} is not a valid camera plugin'.format(camera_plugin)
|
assert cls and issubclass(
|
||||||
|
cls, CameraPlugin
|
||||||
|
), '{} is not a valid camera plugin'.format(camera_plugin)
|
||||||
return cls(**config)
|
return cls(**config)
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
|
||||||
@action
|
@action
|
||||||
def generate(self, content: str, output_file: Optional[str] = None, show: bool = False,
|
def generate(
|
||||||
format: str = 'png') -> QrcodeGeneratedResponse:
|
self,
|
||||||
|
content: str,
|
||||||
|
output_file: Optional[str] = None,
|
||||||
|
show: bool = False,
|
||||||
|
format: str = 'png',
|
||||||
|
) -> QrcodeGeneratedResponse:
|
||||||
"""
|
"""
|
||||||
Generate a QR code.
|
Generate a QR code.
|
||||||
If you configured the :class:`platypush.backend.http.HttpBackend` then you can also generate
|
If you configured the :class:`platypush.backend.http.HttpBackend` then you can also generate
|
||||||
|
@ -65,6 +77,7 @@ class QrcodePlugin(Plugin):
|
||||||
:return: :class:`platypush.message.response.qrcode.QrcodeGeneratedResponse`.
|
:return: :class:`platypush.message.response.qrcode.QrcodeGeneratedResponse`.
|
||||||
"""
|
"""
|
||||||
import qrcode
|
import qrcode
|
||||||
|
|
||||||
qr = qrcode.make(content)
|
qr = qrcode.make(content)
|
||||||
img = qr.get_image()
|
img = qr.get_image()
|
||||||
ret = {
|
ret = {
|
||||||
|
@ -105,17 +118,26 @@ class QrcodePlugin(Plugin):
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
assert isinstance(frame, np.ndarray), \
|
assert isinstance(
|
||||||
'Image conversion only works with numpy arrays for now (got {})'.format(type(frame))
|
frame, np.ndarray
|
||||||
|
), 'Image conversion only works with numpy arrays for now (got {})'.format(
|
||||||
|
type(frame)
|
||||||
|
)
|
||||||
mode = 'RGB'
|
mode = 'RGB'
|
||||||
if len(frame.shape) > 2 and frame.shape[2] == 4:
|
if len(frame.shape) > 2 and frame.shape[2] == 4:
|
||||||
mode = 'RGBA'
|
mode = 'RGBA'
|
||||||
|
|
||||||
return Image.frombuffer(mode, (frame.shape[1], frame.shape[0]), frame, 'raw', mode, 0, 1)
|
return Image.frombuffer(
|
||||||
|
mode, (frame.shape[1], frame.shape[0]), frame, 'raw', mode, 0, 1
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def start_scanning(self, camera_plugin: Optional[str] = None, duration: Optional[float] = None,
|
def start_scanning(
|
||||||
n_codes: Optional[int] = None) -> Optional[List[ResultModel]]:
|
self,
|
||||||
|
camera_plugin: Optional[str] = None,
|
||||||
|
duration: Optional[float] = None,
|
||||||
|
n_codes: Optional[int] = None,
|
||||||
|
) -> Optional[List[ResultModel]]:
|
||||||
"""
|
"""
|
||||||
Decode QR-codes and bar codes using a camera.
|
Decode QR-codes and bar codes using a camera.
|
||||||
|
|
||||||
|
@ -130,6 +152,7 @@ class QrcodePlugin(Plugin):
|
||||||
:class:`platypush.message.response.qrcode.ResultModel` instances with the scanned results,
|
:class:`platypush.message.response.qrcode.ResultModel` instances with the scanned results,
|
||||||
"""
|
"""
|
||||||
from pyzbar import pyzbar
|
from pyzbar import pyzbar
|
||||||
|
|
||||||
assert not self._capturing.is_set(), 'A capturing process is already running'
|
assert not self._capturing.is_set(), 'A capturing process is already running'
|
||||||
|
|
||||||
camera = self._get_camera(camera_plugin)
|
camera = self._get_camera(camera_plugin)
|
||||||
|
@ -143,9 +166,11 @@ class QrcodePlugin(Plugin):
|
||||||
with camera:
|
with camera:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
while self._capturing.is_set() \
|
while (
|
||||||
and (not duration or time.time() < start_time + duration) \
|
self._capturing.is_set()
|
||||||
and (not n_codes or len(codes) < n_codes):
|
and (not duration or time.time() < start_time + duration)
|
||||||
|
and (not n_codes or len(codes) < n_codes)
|
||||||
|
):
|
||||||
output = camera.get_stream()
|
output = camera.get_stream()
|
||||||
with output.ready:
|
with output.ready:
|
||||||
output.ready.wait()
|
output.ready.wait()
|
||||||
|
@ -153,15 +178,21 @@ class QrcodePlugin(Plugin):
|
||||||
results = pyzbar.decode(img)
|
results = pyzbar.decode(img)
|
||||||
if results:
|
if results:
|
||||||
results = [
|
results = [
|
||||||
result for result in QrcodeDecodedResponse(results).output['results']
|
result
|
||||||
|
for result in QrcodeDecodedResponse(results).output[
|
||||||
|
'results'
|
||||||
|
]
|
||||||
if result['data'] not in last_results
|
if result['data'] not in last_results
|
||||||
or time.time() >= last_results_time + last_results_timeout
|
or time.time()
|
||||||
|
>= last_results_time + last_results_timeout
|
||||||
]
|
]
|
||||||
|
|
||||||
if results:
|
if results:
|
||||||
codes.extend(results)
|
codes.extend(results)
|
||||||
get_bus().post(QrcodeScannedEvent(results=results))
|
get_bus().post(QrcodeScannedEvent(results=results))
|
||||||
last_results = {result['data']: result for result in results}
|
last_results = {
|
||||||
|
result['data']: result for result in results
|
||||||
|
}
|
||||||
last_results_time = time.time()
|
last_results_time = time.time()
|
||||||
finally:
|
finally:
|
||||||
self._capturing.clear()
|
self._capturing.clear()
|
||||||
|
@ -172,4 +203,5 @@ class QrcodePlugin(Plugin):
|
||||||
def stop_scanning(self):
|
def stop_scanning(self):
|
||||||
self._capturing.clear()
|
self._capturing.clear()
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
Loading…
Reference in a new issue