diff --git a/docs/source/backends.rst b/docs/source/backends.rst index eedc39cd..49cdeac9 100644 --- a/docs/source/backends.rst +++ b/docs/source/backends.rst @@ -19,6 +19,7 @@ Backends platypush/backend/button.flic.rst platypush/backend/camera.pi.rst platypush/backend/chat.telegram.rst + platypush/backend/clipboard.rst platypush/backend/covid19.rst platypush/backend/foursquare.rst platypush/backend/google.fit.rst diff --git a/docs/source/events.rst b/docs/source/events.rst index e1edcb4b..9196bace 100644 --- a/docs/source/events.rst +++ b/docs/source/events.rst @@ -15,6 +15,7 @@ Events platypush/events/button.flic.rst platypush/events/camera.rst platypush/events/chat.telegram.rst + platypush/events/clipboard.rst platypush/events/covid19.rst platypush/events/distance.rst platypush/events/foursquare.rst diff --git a/docs/source/platypush/backend/clipboard.rst b/docs/source/platypush/backend/clipboard.rst new file mode 100644 index 00000000..8d6ea0a3 --- /dev/null +++ b/docs/source/platypush/backend/clipboard.rst @@ -0,0 +1,5 @@ +``platypush.backend.clipboard`` +=============================== + +.. automodule:: platypush.backend.clipboard + :members: diff --git a/docs/source/platypush/events/clipboard.rst b/docs/source/platypush/events/clipboard.rst new file mode 100644 index 00000000..34ba60b6 --- /dev/null +++ b/docs/source/platypush/events/clipboard.rst @@ -0,0 +1,5 @@ +``platypush.message.event.clipboard`` +===================================== + +.. automodule:: platypush.message.event.clipboard + :members: diff --git a/platypush/backend/clipboard.py b/platypush/backend/clipboard.py new file mode 100644 index 00000000..ece09b5d --- /dev/null +++ b/platypush/backend/clipboard.py @@ -0,0 +1,34 @@ +import time +from typing import Optional + +import pyperclip + +from platypush.backend import Backend +from platypush.message.event.clipboard import ClipboardEvent + + +class ClipboardBackend(Backend): + """ + This backend monitors for changes in the clipboard and generates even when the user copies a new text. + + Requires: + + - **pyperclip** (``pip install pyperclip``) + + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._last_text: Optional[str] = None + + def run(self): + while not self.should_stop(): + text = pyperclip.paste() + if text and text != self._last_text: + self.bus.post(ClipboardEvent(text=text)) + + self._last_text = text + time.sleep(0.1) + + +# vim:sw=4:ts=4:et: diff --git a/platypush/message/event/clipboard.py b/platypush/message/event/clipboard.py new file mode 100644 index 00000000..4fc4fa74 --- /dev/null +++ b/platypush/message/event/clipboard.py @@ -0,0 +1,9 @@ +from platypush.message.event import Event + + +class ClipboardEvent(Event): + def __init__(self, text: str, *args, **kwargs): + super().__init__(*args, text=text, **kwargs) + + +# vim:sw=4:ts=4:et: diff --git a/platypush/plugins/pushbullet.py b/platypush/plugins/pushbullet.py index 9763e76c..3e7ccf2a 100644 --- a/platypush/plugins/pushbullet.py +++ b/platypush/plugins/pushbullet.py @@ -1,5 +1,7 @@ import json import os +from typing import Optional + import requests from platypush.context import get_backend @@ -18,12 +20,12 @@ class PushbulletPlugin(Plugin): * The :class:`platypush.backend.pushbullet.Pushbullet` backend enabled """ - def __init__(self, token: str = None, *args, **kwargs): + def __init__(self, token: str = None, **kwargs): """ :param token: Pushbullet API token. If not set the plugin will try to retrieve it from the Pushbullet backend configuration, if available """ - super().__init__(*args, **kwargs) + super().__init__(**kwargs) if not token: backend = get_backend('pushbullet') @@ -44,18 +46,18 @@ class PushbulletPlugin(Plugin): Get the list of available devices """ resp = requests.get('https://api.pushbullet.com/v2/devices', - headers={'Authorization': 'Bearer ' + self.token, - 'Content-Type': 'application/json'}) + headers={'Authorization': 'Bearer ' + self.token, + 'Content-Type': 'application/json'}) self._devices = resp.json().get('devices', []) self._devices_by_id = { - dev['iden']: dev - for dev in self._devices + dev['iden']: dev + for dev in self._devices } self._devices_by_name = { - dev['nickname']: dev - for dev in self._devices if 'nickname' in dev + dev['nickname']: dev + for dev in self._devices if 'nickname' in dev } @action @@ -124,7 +126,6 @@ class PushbulletPlugin(Plugin): if not device: raise RuntimeError('No such device') - pushbullet = get_backend('pushbullet') resp = requests.post('https://api.pushbullet.com/v2/upload-request', data=json.dumps({'file_name': os.path.basename(filename)}), headers={'Authorization': 'Bearer ' + self.token, @@ -151,7 +152,7 @@ class PushbulletPlugin(Plugin): 'device_iden': device['iden'] if device else None, 'file_name': r['file_name'], 'file_type': r['file_type'], - 'file_url': r['file_url'] })) + 'file_url': r['file_url']})) if resp.status_code >= 400: raise Exception('Pushbullet file push failed with status {}'. @@ -163,6 +164,29 @@ class PushbulletPlugin(Plugin): 'url': r['file_url'] } + @action + def send_clipboard(self, text: str): + """ + Copy text to the clipboard of a device. + + :param text: Text to be copied. + """ + backend = get_backend('pushbullet') + device_id = backend.get_device_id() if backend else None + + resp = requests.post('https://api.pushbullet.com/v2/ephemerals', + data=json.dumps({ + 'type': 'push', + 'push': { + 'body': text, + 'type': 'clip', + 'source_device_iden': device_id, + }, + }), + headers={'Authorization': 'Bearer ' + self.token, + 'Content-Type': 'application/json'}) + + resp.raise_for_status() + # vim:sw=4:ts=4:et: - diff --git a/requirements.txt b/requirements.txt index 8c7a8fe6..26bf9346 100644 --- a/requirements.txt +++ b/requirements.txt @@ -264,3 +264,6 @@ croniter # Support for SSH integration # paramiko + +# Support for clipboard integration +# pyperclip diff --git a/setup.py b/setup.py index d874c2de..93a526e1 100755 --- a/setup.py +++ b/setup.py @@ -301,5 +301,7 @@ setup( 'samsungtv': ['samsungtvws'], # Support for SSH integration 'ssh': ['paramiko'], + # Support for clipboard integration + 'clipboard': ['pyperclip'], }, )