[#61] Plugins actions refactoring

- Using `@action` annotation to indicate methods that are allowed to be
executed as actions

- The output and errors of an action are automatically wrapped into a
`Response` object without any response build required on the plugin side
This commit is contained in:
Fabio Manganiello 2018-07-06 02:08:38 +02:00
parent 81a81312e3
commit 66d78c8615
42 changed files with 386 additions and 216 deletions

View File

@ -60,6 +60,7 @@ class Config(object):
self._config = self._read_config_file(self._cfgfile)
if 'token' in self._config:
self._config['token'] = self._config['token']
self._config['token_hash'] = get_hash(self._config['token'])
if 'workdir' not in self._config:

View File

@ -86,6 +86,11 @@ class EventAction(Request):
if 'target' not in action:
action['target'] = action['origin']
token = Config.get('token')
if token:
action['token'] = token
return super().build(action)

View File

@ -193,7 +193,7 @@ class Request(Message):
raise RuntimeError('Response processed with errors: {}'.format(response))
logger.info('Processed response from plugin {}: {}'.
format(plugin, response))
format(plugin, str(response)))
except Exception as e:
# Retry mechanism
response = Response(output=None, errors=[str(e), traceback.format_exc()])

View File

@ -1,13 +1,26 @@
import sys
import logging
import traceback
from platypush.config import Config
from platypush.message.response import Response
from platypush.utils import get_decorators
def action(f):
def _execute_action(*args, **kwargs):
return f(*args, **kwargs)
output = None
errors = []
try:
output = f(*args, **kwargs)
except Exception as e:
if isinstance(args[0], Plugin):
args[0].logger.exception(e)
errors.append(str(e) + '\n' + traceback.format_exc())
return Response(output=output, errors=errors)
return _execute_action
@ -19,7 +32,13 @@ class Plugin(object):
if 'logging' in kwargs:
self.logger.setLevel(getattr(logging, kwargs['logging'].upper()))
self.registered_actions = set(get_decorators(self.__class__).get('action', []))
def run(self, method, *args, **kwargs):
if method not in self.registered_actions:
raise RuntimeError('{} is not a registered action on {}'.format(
method, self.__class__.__name__))
return getattr(self, method)(*args, **kwargs)

View File

@ -3,9 +3,7 @@
"""
from platypush.context import get_backend
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class AssistantGooglePlugin(Plugin):
"""
@ -14,21 +12,25 @@ class AssistantGooglePlugin(Plugin):
backend to programmatically control the conversation status.
"""
def __init__(*args, **kwargs):
super().__init__(*args, **kwargs)
@action
def start_conversation(self):
"""
Programmatically start a conversation with the assistant
"""
assistant = get_backend('assistant.google')
assistant.start_conversation()
return Response(output='', errors=[])
@action
def stop_conversation(self):
"""
Programmatically stop a running conversation with the assistant
"""
assistant = get_backend('assistant.google')
assistant.stop_conversation()
return Response(output='', errors=[])
# vim:sw=4:ts=4:et:

View File

@ -3,9 +3,7 @@
"""
from platypush.context import get_backend
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class AssistantGooglePushtotalkPlugin(Plugin):
"""
@ -14,21 +12,24 @@ class AssistantGooglePushtotalkPlugin(Plugin):
:mod:`platypush.backend.assistant.google.pushtotalk` backend.
"""
def __init__(*args, **kwargs):
super().__init__(*args, **kwargs)
@action
def start_conversation(self):
"""
Programmatically start a conversation with the assistant
"""
assistant = get_backend('assistant.google.pushtotalk')
assistant.start_conversation()
return Response(output='', errors=[])
@action
def stop_conversation(self):
"""
Programmatically stop a running conversation with the assistant
"""
assistant = get_backend('assistant.google.pushtotalk')
assistant.stop_conversation()
return Response(output='', errors=[])
# vim:sw=4:ts=4:et:

View File

@ -7,8 +7,7 @@ import importlib
from abc import ABCMeta, abstractmethod
from platypush.plugins import Plugin
from platypush.message.response import Response
from platypush.plugins import Plugin, action
class CalendarInterface:
@ -64,6 +63,7 @@ class CalendarPlugin(Plugin, CalendarInterface):
self.calendars.append(getattr(module, class_name)(**calendar))
@action
def get_upcoming_events(self, max_results=10):
"""
Get a list of upcoming events merging all the available calendars.
@ -116,7 +116,7 @@ class CalendarPlugin(Plugin, CalendarInterface):
else event['start']['date'] + 'T00:00:00+00:00'
))[:max_results]
return Response(output=events)
return events
# vim:sw=4:ts=4:et:

View File

@ -9,8 +9,7 @@ import pytz
from icalendar import Calendar, Event
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
from platypush.plugins.calendar import CalendarInterface
@ -61,6 +60,7 @@ class IcalCalendarPlugin(Plugin, CalendarInterface):
}
@action
def get_upcoming_events(self, max_results=10, only_participating=True):
"""
Get the upcoming events. See
@ -90,7 +90,7 @@ class IcalCalendarPlugin(Plugin, CalendarInterface):
else:
self.logger.error("HTTP error while getting {}: {}".format(self.url, response))
return Response(output=events)
return events
# vim:sw=4:ts=4:et:

View File

@ -3,9 +3,7 @@
"""
from platypush.context import get_backend
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class CameraPiPlugin(Plugin):
@ -15,22 +13,26 @@ class CameraPiPlugin(Plugin):
to programmatically control the status.
"""
def __init__(*args, **kwargs):
super().__init__(*args, **kwargs)
@action
def start_recording(self):
"""
Start recording
"""
camera = get_backend('camera.pi')
camera.send_camera_action(camera.CameraAction.START_RECORDING)
return Response(output={'status':'ok'})
@action
def stop_recording(self):
"""
Stop recording
"""
camera = get_backend('camera.pi')
camera.send_camera_action(camera.CameraAction.STOP_RECORDING)
return Response(output={'status':'ok'})
@action
def take_picture(self, image_file):
"""
Take a picture.
@ -40,7 +42,7 @@ class CameraPiPlugin(Plugin):
"""
camera = get_backend('camera.pi')
camera.send_camera_action(camera.CameraAction.TAKE_PICTURE, image_file=image_file)
return Response(output={'image_file':image_file})
return {'image_file': image_file}
# vim:sw=4:ts=4:et:

View File

@ -4,9 +4,7 @@
from sqlalchemy import create_engine, Table, MetaData
from platypush.message.response import Response
from .. import Plugin
from platypush.plugins import Plugin, action
class DbPlugin(Plugin):
"""
@ -40,6 +38,7 @@ class DbPlugin(Plugin):
else:
return self.engine
@action
def execute(self, statement, engine=None, *args, **kwargs):
"""
Executes a raw SQL statement.
@ -65,9 +64,8 @@ class DbPlugin(Plugin):
result = connection.execute(statement)
connection.commit()
return Response()
@action
def select(self, query, engine=None, *args, **kwargs):
"""
Returns rows (as a list of hashes) given a query.
@ -119,9 +117,10 @@ class DbPlugin(Plugin):
for row in result.fetchall()
]
return Response(output=rows)
return rows
@action
def insert(self, table, records, engine=None, *args, **kwargs):
"""
Inserts records (as a list of hashes) into a table.
@ -169,8 +168,6 @@ class DbPlugin(Plugin):
insert = table.insert().values(**record)
engine.execute(insert)
return Response()
# vim:sw=4:ts=4:et:

View File

@ -9,7 +9,7 @@ import os
from apiclient import discovery
from platypush.message.response import Response
from platypush.plugins import action
from platypush.plugins.google import GooglePlugin
from platypush.plugins.calendar import CalendarInterface
@ -25,6 +25,7 @@ class GoogleCalendarPlugin(GooglePlugin, CalendarInterface):
super().__init__(scopes=self.scopes, *args, **kwargs)
@action
def get_upcoming_events(self, max_results=10):
"""
Get the upcoming events. See

View File

@ -17,7 +17,7 @@ from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from platypush.message.response import Response
from platypush.plugins import action
from platypush.plugins.google import GooglePlugin
@ -32,6 +32,7 @@ class GoogleMailPlugin(GooglePlugin):
super().__init__(scopes=self.scopes, *args, **kwargs)
@action
def compose(self, sender, to, subject, body, files=None):
"""
Compose a message.
@ -91,9 +92,10 @@ class GoogleMailPlugin(GooglePlugin):
message = (service.users().messages().send(
userId='me', body=body).execute())
return Response(output=message)
return message
@action
def get_labels(self):
"""
Returns the available labels on the GMail account
@ -101,7 +103,7 @@ class GoogleMailPlugin(GooglePlugin):
service = self._get_service()
results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
return Response(output=labels)
return labels
def _get_service(self):

View File

@ -5,7 +5,7 @@
import json
import requests
from platypush.message.response import Response
from platypush.plugins import action
from platypush.plugins.google import GooglePlugin
@ -26,6 +26,7 @@ class GoogleMapsPlugin(GooglePlugin):
self.api_key = api_key
@action
def get_address_from_latlng(self, latitude, longitude):
"""
Get an address information given lat/long
@ -65,7 +66,7 @@ class GoogleMapsPlugin(GooglePlugin):
elif component_type == 'postal_code':
address['postal_code'] = addr_component['long_name']
return Response(output=address)
return address
# vim:sw=4:ts=4:et:

View File

@ -5,8 +5,7 @@
import threading
import time
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class GpioPlugin(Plugin):
@ -17,6 +16,10 @@ class GpioPlugin(Plugin):
* **RPi.GPIO** (`pip install RPi.GPIO`)
"""
def __init__(*args, **kwargs):
super().__init__(*args, **kwargs)
@action
def write(self, pin, val):
"""
Write a byte value to a pin.
@ -44,12 +47,13 @@ class GpioPlugin(Plugin):
gpio.setup(pin, gpio.OUT)
gpio.output(pin, val)
return Response(output={
return {
'pin': pin,
'val': val,
'method': 'write',
})
}
@action
def read(self, pin):
"""
Reads a value from a PIN.
@ -74,11 +78,11 @@ class GpioPlugin(Plugin):
gpio.setup(pin, gpio.IN)
val = gpio.input(pin)
return Response(output={
return {
'pin': pin,
'val': val,
'method': 'read',
})
}
# vim:sw=4:ts=4:et:

View File

@ -1,4 +1,4 @@
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class GpioSensorPlugin(Plugin):
@ -10,6 +10,7 @@ class GpioSensorPlugin(Plugin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@action
def get_measurement(self, *args, **kwargs):
"""
Implemented by the subclasses.
@ -27,6 +28,7 @@ class GpioSensorPlugin(Plugin):
"""
raise NotImplementedError('get_measurement should be implemented in a derived class')
@action
def get_data(self, *args, **kwargs):
"""
Alias for ``get_measurement``

View File

@ -1,7 +1,7 @@
import threading
import time
from platypush.message.response import Response
from platypush.plugins import action
from platypush.plugins.gpio.sensor import GpioSensorPlugin
@ -39,6 +39,7 @@ class GpioSensorDistancePlugin(GpioSensorPlugin):
gpio.output(self.trigger_pin, False)
@action
def get_measurement(self):
"""
Extends :func:`.GpioSensorPlugin.get_measurement`

View File

@ -1,8 +1,8 @@
import enum
import time
from platypush.plugins import action
from platypush.plugins.gpio.sensor import GpioSensorPlugin
from platypush.message.response import Response
class MCP3008Mode(enum.Enum):
@ -125,6 +125,7 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
return (value * self.Vdd) / 1023.0 if value is not None else None
@action
def get_measurement(self):
"""
Returns a measurement from the sensors connected to the MCP3008 device.
@ -164,7 +165,7 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
else:
values[i] = value
return Response(output=values)
return values
# vim:sw=4:ts=4:et:

View File

@ -2,8 +2,7 @@ import enum
import threading
import time
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
from platypush.context import get_plugin
from platypush.config import Config
@ -95,7 +94,7 @@ class GpioZeroborgPlugin(Plugin):
value = None
while value is None:
value = plugin.get_measurement()
value = plugin.get_measurement().output
if time.time() - measure_start_time > timeout:
return None
@ -127,6 +126,7 @@ class GpioZeroborgPlugin(Plugin):
return direction
@action
def drive(self, direction):
"""
Drive the motors in a certain direction.
@ -184,9 +184,10 @@ class GpioZeroborgPlugin(Plugin):
self._drive_thread = threading.Thread(target=_run)
self._drive_thread.start()
return Response(output={'status': 'running', 'direction': direction})
return {'status': 'running', 'direction': direction}
@action
def stop(self):
"""
Turns off the motors
@ -199,7 +200,7 @@ class GpioZeroborgPlugin(Plugin):
self.zb.MotorsOff()
self.zb.ResetEpo()
return Response(output={'status':'stopped'})
return {'status':'stopped'}
# vim:sw=4:ts=4:et:

View File

@ -1,8 +1,6 @@
import requests
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class HttpRequestPlugin(Plugin):
"""
@ -41,6 +39,9 @@ class HttpRequestPlugin(Plugin):
}
"""
def __init__(*args, **kwargs):
super().__init__(*args, **kwargs)
def _exec(self, method, url, output='text', **kwargs):
""" Available output types: text (default), json, binary """
@ -51,9 +52,10 @@ class HttpRequestPlugin(Plugin):
if output == 'json': output = response.json()
if output == 'binary': output = response.content
return Response(output=output, errors=[])
return output
@action
def get(self, url, **kwargs):
"""
Perform a GET request
@ -68,6 +70,7 @@ class HttpRequestPlugin(Plugin):
return self._exec(method='get', url=url, **kwargs)
@action
def post(self, url, **kwargs):
"""
Perform a POST request
@ -82,6 +85,7 @@ class HttpRequestPlugin(Plugin):
return self._exec(method='post', url=url, **kwargs)
@action
def head(self, url, **kwargs):
"""
Perform an HTTP HEAD request
@ -96,6 +100,7 @@ class HttpRequestPlugin(Plugin):
return self._exec(method='head', url=url, **kwargs)
@action
def put(self, url, **kwargs):
"""
Perform a PUT request
@ -110,6 +115,7 @@ class HttpRequestPlugin(Plugin):
return self._exec(method='put', url=url, **kwargs)
@action
def delete(self, url, **kwargs):
"""
Perform a DELETE request
@ -124,6 +130,7 @@ class HttpRequestPlugin(Plugin):
return self._exec(method='delete', url=url, **kwargs)
@action
def options(self, url, **kwargs):
"""
Perform an HTTP OPTIONS request

View File

@ -1,6 +1,7 @@
import datetime
import dateutil.parser
from platypush.plugins import action
from platypush.plugins.http.request import HttpRequestPlugin
class HttpRequestOtaBookingPlugin(HttpRequestPlugin):
@ -12,6 +13,7 @@ class HttpRequestOtaBookingPlugin(HttpRequestPlugin):
self.timeout = timeout
@action
def get_reservations(self, day='today'):
url = 'https://hub-api.booking.com/v1/hotels/{}/reservations' \
.format(self.hotel_id)

View File

@ -1,9 +1,7 @@
import pylast
import time
from platypush.message.response import Response
from .. import Plugin
from platypush.plugins import Plugin, action
class LastfmPlugin(Plugin):
"""
@ -42,6 +40,7 @@ class LastfmPlugin(Plugin):
password_hash = pylast.md5(self.password))
@action
def scrobble(self, artist, title, album=None, **kwargs):
"""
Scrobble a track to Last.FM
@ -61,8 +60,6 @@ class LastfmPlugin(Plugin):
timestamp = int(time.time()),
)
return Response()
def update_now_playing(self, artist, title, album=None, **kwargs):
"""
@ -82,8 +79,6 @@ class LastfmPlugin(Plugin):
album = album,
)
return Response()
# vim:sw=4:ts=4:et:

View File

@ -1,22 +1,26 @@
from .. import Plugin
from platypush.plugins import Plugin, action
class LightPlugin(Plugin):
"""
Abstract plugin to interface your logic with lights/bulbs.
"""
@action
def on(self):
""" Turn the light on """
raise NotImplementedError()
@action
def off(self):
""" Turn the light off """
raise NotImplementedError()
@action
def toggle(self):
""" Toggle the light status (on/off) """
raise NotImplementedError()
@action
def status(self):
""" Get the light status """
raise NotImplementedError()

View File

@ -8,9 +8,9 @@ from redis.exceptions import TimeoutError as QueueTimeoutError
from phue import Bridge
from platypush.context import get_backend
from platypush.message.response import Response
from platypush.plugins import action
from platypush.plugins.light import LightPlugin
from .. import LightPlugin
class LightHuePlugin(LightPlugin):
"""
@ -75,6 +75,7 @@ class LightHuePlugin(LightPlugin):
for g in groups:
self.lights.extend([l.name for l in g.lights])
@action
def connect(self):
"""
Connect to the configured Hue bridge. If the device hasn't been paired
@ -100,6 +101,7 @@ class LightHuePlugin(LightPlugin):
self.logger.info('Bridge already connected')
@action
def get_scenes(self):
"""
Get the available scenes on the devices.
@ -129,9 +131,10 @@ class LightHuePlugin(LightPlugin):
}
"""
return Response(output=self.bridge.get_scene())
return self.bridge.get_scene()
@action
def get_lights(self):
"""
Get the configured lights.
@ -170,9 +173,10 @@ class LightHuePlugin(LightPlugin):
}
"""
return Response(output=self.bridge.get_light())
return self.bridge.get_light()
@action
def get_groups(self):
"""
Get the list of configured light groups.
@ -222,7 +226,7 @@ class LightHuePlugin(LightPlugin):
}
"""
return Response(output=self.bridge.get_group())
return self.bridge.get_group()
def _exec(self, attr, *args, **kwargs):
@ -257,8 +261,8 @@ class LightHuePlugin(LightPlugin):
self.bridge = None
raise e
return Response(output='ok')
@action
def set_light(self, light, **kwargs):
"""
Set a light (or lights) property.
@ -281,8 +285,8 @@ class LightHuePlugin(LightPlugin):
self.connect()
self.bridge.set_light(light, **kwargs)
return Response(output='ok')
@action
def set_group(self, group, **kwargs):
"""
Set a group (or groups) property.
@ -305,8 +309,8 @@ class LightHuePlugin(LightPlugin):
self.connect()
self.bridge.set_group(group, **kwargs)
return Response(output='ok')
@action
def on(self, lights=[], groups=[]):
"""
Turn lights/groups on.
@ -317,6 +321,7 @@ class LightHuePlugin(LightPlugin):
return self._exec('on', True, lights=lights, groups=groups)
@action
def off(self, lights=[], groups=[]):
"""
Turn lights/groups off.
@ -327,6 +332,7 @@ class LightHuePlugin(LightPlugin):
return self._exec('on', False, lights=lights, groups=groups)
@action
def bri(self, value, lights=[], groups=[]):
"""
Set lights/groups brightness.
@ -339,6 +345,7 @@ class LightHuePlugin(LightPlugin):
return self._exec('bri', int(value) % (self.MAX_BRI+1),
lights=lights, groups=groups)
@action
def sat(self, value, lights=[], groups=[]):
"""
Set lights/groups saturation.
@ -351,6 +358,7 @@ class LightHuePlugin(LightPlugin):
return self._exec('sat', int(value) % (self.MAX_SAT+1),
lights=lights, groups=groups)
@action
def hue(self, value, lights=[], groups=[]):
"""
Set lights/groups color hue.
@ -363,6 +371,7 @@ class LightHuePlugin(LightPlugin):
return self._exec('hue', int(value) % (self.MAX_HUE+1),
lights=lights, groups=groups)
@action
def scene(self, name, lights=[], groups=[]):
"""
Set a scene by name.
@ -374,6 +383,7 @@ class LightHuePlugin(LightPlugin):
return self._exec('scene', name=name, lights=lights, groups=groups)
@action
def is_animation_running(self):
"""
:returns: True if there is an animation running, false otherwise.
@ -381,6 +391,7 @@ class LightHuePlugin(LightPlugin):
return self.animation_thread is not None
@action
def stop_animation(self):
"""
Stop a running animation if any
@ -389,6 +400,7 @@ class LightHuePlugin(LightPlugin):
if self.animation_thread and self.animation_thread.is_alive():
self.redis.rpush(self.ANIMATION_CTRL_QUEUE_NAME, 'STOP')
@action
def animate(self, animation, duration=None,
hue_range=[0, MAX_HUE], sat_range=[0, MAX_SAT],
bri_range=[MAX_BRI-1, MAX_BRI], lights=None, groups=None,
@ -529,7 +541,6 @@ class LightHuePlugin(LightPlugin):
self.stop_animation()
self.animation_thread = Thread(target=_animate_thread, args=(lights,))
self.animation_thread.start()
return Response(output='ok')
# vim:sw=4:ts=4:et:

View File

@ -2,10 +2,9 @@ import re
import subprocess
from platypush.context import get_plugin
from platypush.message.response import Response
from platypush.plugins.media import PlayerState
from .. import Plugin
from platypush.plugins import Plugin, action
class MediaCtrlPlugin(Plugin):
"""
@ -76,9 +75,9 @@ class MediaCtrlPlugin(Plugin):
return None
@action
def play(self, url):
(type, resource) = self._get_type_and_resource_by_url(url)
response = Response(output='', errors = [])
plugin_name = None
if type == 'mpd':
@ -99,11 +98,13 @@ class MediaCtrlPlugin(Plugin):
self.url = resource
return self.plugin.play(resource)
@action
def pause(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.pause()
@action
def stop(self):
plugin = self._get_playing_plugin()
if plugin:
@ -112,36 +113,43 @@ class MediaCtrlPlugin(Plugin):
return ret
@action
def voldown(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.voldown()
@action
def volup(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.volup()
@action
def back(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.back()
@action
def forward(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.forward()
@action
def next(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.next()
@action
def previous(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.previous()
@action
def status(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.status()

View File

@ -1,8 +1,7 @@
import rtmidi
import time
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class MidiPlugin(Plugin):
@ -39,6 +38,7 @@ class MidiPlugin(Plugin):
format(self.device_name))
@action
def send_message(self, values, *args, **kwargs):
"""
:param values: Values is expected to be a list containing the MIDI command code and the command parameters - see reference at https://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html
@ -70,9 +70,9 @@ class MidiPlugin(Plugin):
"""
self.midiout.send_message(values, *args, **kwargs)
return Response(output={'status':'ok'})
@action
def play_note(self, note, velocity, duration=0):
"""
Play a note with selected velocity and duration.
@ -96,6 +96,7 @@ class MidiPlugin(Plugin):
self._played_notes.remove(note)
@action
def release_note(self, note):
"""
Release a played note.
@ -108,6 +109,7 @@ class MidiPlugin(Plugin):
self._played_notes.remove(note)
@action
def release_all_notes(self):
"""
Release all the notes being played.

View File

@ -2,8 +2,7 @@ import json
import paho.mqtt.publish as publisher
from platypush.message import Message
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class MqttPlugin(Plugin):
@ -12,6 +11,7 @@ class MqttPlugin(Plugin):
with the MQTT protocol, see http://mqtt.org/
"""
@action
def send_message(self, topic, msg, host, port=1883, *args, **kwargs):
"""
Sends a message to a topic/channel.
@ -35,7 +35,6 @@ class MqttPlugin(Plugin):
except: pass
publisher.single(topic, str(msg), hostname=host, port=port)
return Response(output={'state': 'ok'})
# vim:sw=4:ts=4:et:

View File

@ -1,33 +1,47 @@
from .. import Plugin
from platypush.plugins import Plugin, action
class MusicPlugin(Plugin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@action
def play(self):
raise NotImplementedError()
@action
def pause(self):
raise NotImplementedError()
@action
def stop(self):
raise NotImplementedError()
@action
def next(self):
raise NotImplementedError()
@action
def previous(self):
raise NotImplementedError()
@action
def setvol(self, vol):
raise NotImplementedError()
@action
def add(self, content):
raise NotImplementedError()
@action
def playlistadd(self, playlist):
raise NotImplementedError()
@action
def clear(self):
raise NotImplementedError()
@action
def status(self):
raise NotImplementedError()

View File

@ -1,9 +1,8 @@
import mpd
import re
from platypush.message.response import Response
from .. import MusicPlugin
from platypush.plugins import action
from platypush.plugins.music import MusicPlugin
class MusicMpdPlugin(MusicPlugin):
"""
@ -29,6 +28,7 @@ class MusicMpdPlugin(MusicPlugin):
:type port: int
"""
super().__init__()
self.host = host
self.port = port
self.client = mpd.MPDClient(use_unicode=True)
@ -36,8 +36,9 @@ class MusicMpdPlugin(MusicPlugin):
def _exec(self, method, *args, **kwargs):
getattr(self.client, method)(*args, **kwargs)
return self.status()
return self.status().output
@action
def play(self, resource=None):
"""
Play a resource by path/URI
@ -51,6 +52,7 @@ class MusicMpdPlugin(MusicPlugin):
self.add(resource)
return self._exec('play')
@action
def play_pos(self, pos):
"""
Play a track in the current playlist by position number
@ -61,6 +63,7 @@ class MusicMpdPlugin(MusicPlugin):
return self._exec('play', pos)
@action
def pause(self):
""" Pause playback """
@ -68,31 +71,38 @@ class MusicMpdPlugin(MusicPlugin):
if status == 'play': return self._exec('pause')
else: return self._exec('play')
@action
def pause_if_playing(self):
""" Pause playback only if it's playing """
status = self.status().output['state']
if status == 'play': return self._exec('pause')
else: return Response(output={})
if status == 'play':
return self._exec('pause')
@action
def play_if_paused(self):
""" Play only if it's paused (resume) """
status = self.status().output['state']
if status == 'pause': return self._exec('play')
else: return Response(output={})
if status == 'pause':
return self._exec('play')
@action
def stop(self):
""" Stop playback """
return self._exec('stop')
@action
def play_or_stop(self):
""" Play or stop (play state toggle) """
status = self.status().output['state']
if status == 'play': return self._exec('stop')
else: return self._exec('play')
if status == 'play':
return self._exec('stop')
else:
return self._exec('play')
@action
def playid(self, track_id):
"""
Play a track by ID
@ -103,14 +113,17 @@ class MusicMpdPlugin(MusicPlugin):
return self._exec('playid', track_id)
@action
def next(self):
""" Play the next track """
return self._exec('next')
@action
def previous(self):
""" Play the previous track """
return self._exec('previous')
@action
def setvol(self, vol):
"""
Set the volume
@ -120,6 +133,7 @@ class MusicMpdPlugin(MusicPlugin):
"""
return self._exec('setvol', vol)
@action
def volup(self, delta=10):
"""
Turn up the volume
@ -134,6 +148,7 @@ class MusicMpdPlugin(MusicPlugin):
self.setvol(str(new_volume))
return self.status()
@action
def voldown(self, delta=10):
"""
Turn down the volume
@ -148,6 +163,7 @@ class MusicMpdPlugin(MusicPlugin):
self.setvol(str(new_volume))
return self.status()
@action
def random(self, value=None):
"""
Set shuffle mode
@ -161,6 +177,7 @@ class MusicMpdPlugin(MusicPlugin):
value = 1 if value == 0 else 0
return self._exec('random', value)
@action
def repeat(self, value=None):
"""
Set repeat mode
@ -174,6 +191,7 @@ class MusicMpdPlugin(MusicPlugin):
value = 1 if value == 0 else 0
return self._exec('repeat', value)
@action
def add(self, resource):
"""
Add a resource (track, album, artist, folder etc.) to the current playlist
@ -184,6 +202,7 @@ class MusicMpdPlugin(MusicPlugin):
return self._exec('add', resource)
@action
def load(self, playlist):
"""
Load and play a playlist by name
@ -195,10 +214,12 @@ class MusicMpdPlugin(MusicPlugin):
self._exec('load', playlist)
return self.play()
@action
def clear(self):
""" Clear the current playlist """
return self._exec('clear')
@action
def seekcur(self, value):
"""
Seek to the specified position
@ -209,16 +230,19 @@ class MusicMpdPlugin(MusicPlugin):
return self._exec('seekcur', value)
@action
def forward(self):
""" Go forward by 15 seconds """
return self._exec('seekcur', '+15')
@action
def back(self):
""" Go backward by 15 seconds """
return self._exec('seekcur', '-15')
@action
def status(self):
"""
:returns: The current state.
@ -245,8 +269,9 @@ class MusicMpdPlugin(MusicPlugin):
}
"""
return Response(output=self.client.status())
return self.client.status()
@action
def currentsong(self):
"""
:returns: The currently played track.
@ -277,8 +302,9 @@ class MusicMpdPlugin(MusicPlugin):
track['artist'] = m.group(1)
track['title'] = m.group(2)
return Response(output=track)
return track
@action
def playlistinfo(self):
"""
:returns: The tracks in the current playlist as a list of dicts.
@ -315,8 +341,9 @@ class MusicMpdPlugin(MusicPlugin):
]
"""
return Response(output=self.client.playlistinfo())
return self.client.playlistinfo()
@action
def listplaylists(self):
"""
:returns: The playlists available on the server as a list of dicts.
@ -338,17 +365,18 @@ class MusicMpdPlugin(MusicPlugin):
]
"""
return Response(output=sorted(self.client.listplaylists(),
key=lambda p: p['playlist']))
return sorted(self.client.listplaylists(), key=lambda p: p['playlist'])
@action
def lsinfo(self, uri=None):
"""
Returns the list of playlists and directories on the server
"""
output = self.client.lsinfo(uri) if uri else self.client.lsinfo()
return Response(output=output)
return output
@action
def plchanges(self, version):
"""
Show what has changed on the current playlist since a specified playlist
@ -360,8 +388,9 @@ class MusicMpdPlugin(MusicPlugin):
:returns: A list of dicts representing the songs being added since the specified version
"""
return Response(output=self.client.plchanges(version))
return self.client.plchanges(version)
@action
def searchaddplaylist(self, name):
"""
Search and add a playlist by (partial or full) name
@ -379,10 +408,9 @@ class MusicMpdPlugin(MusicPlugin):
self.client.clear()
self.client.load(playlists[0])
self.client.play()
return Response(output={'playlist': playlists[0]})
return Response(output={})
return {'playlist': playlists[0]}
@action
def find(self, filter, *args, **kwargs):
"""
Find in the database/library by filter.
@ -392,9 +420,9 @@ class MusicMpdPlugin(MusicPlugin):
:returns: list[dict]
"""
return Response(
output=self.client.find(*filter, *args, **kwargs))
return self.client.find(*filter, *args, **kwargs)
@action
def findadd(self, filter, *args, **kwargs):
"""
Find in the database/library by filter and add to the current playlist.
@ -404,9 +432,9 @@ class MusicMpdPlugin(MusicPlugin):
:returns: list[dict]
"""
return Response(
output=self.client.findadd(*filter, *args, **kwargs))
return self.client.findadd(*filter, *args, **kwargs)
@action
def search(self, filter, *args, **kwargs):
"""
Free search by filter.
@ -422,8 +450,9 @@ class MusicMpdPlugin(MusicPlugin):
items = sorted(items, key=lambda item:
0 if item['file'].startswith('spotify:') else 1)
return Response(output=items)
return items
@action
def searchadd(self, filter, *args, **kwargs):
"""
Free search by filter and add the results to the current playlist.
@ -433,8 +462,7 @@ class MusicMpdPlugin(MusicPlugin):
:returns: list[dict]
"""
return Response(
output=self.client.searchadd(*filter, *args, **kwargs))
return self.client.searchadd(*filter, *args, **kwargs)
# vim:sw=4:ts=4:et:

View File

@ -3,8 +3,7 @@ import os
import requests
from platypush.context import get_backend
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class PushbulletPlugin(Plugin):
@ -18,6 +17,7 @@ class PushbulletPlugin(Plugin):
* **requests** (``pip install requests``)
"""
@action
def send_push(self, **kwargs):
"""
Send a push.
@ -40,9 +40,8 @@ class PushbulletPlugin(Plugin):
raise Exception('Pushbullet push failed with status {}: {}'.
format(resp.status_code, resp.json()))
return Response(output={'status':'ok'})
@action
def send_file(self, filename):
"""
Send a file.
@ -83,11 +82,11 @@ class PushbulletPlugin(Plugin):
raise Exception('Pushbullet file push failed with status {}'.
format(resp.status_code))
return Response(output={
return {
'filename': r['file_name'],
'type': r['file_type'],
'url': r['file_url']
})
}
# vim:sw=4:ts=4:et:

View File

@ -1,7 +1,6 @@
from redis import Redis
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class RedisPlugin(Plugin):
@ -13,6 +12,7 @@ class RedisPlugin(Plugin):
* **redis** (``pip install redis``)
"""
@action
def send_message(self, queue, msg, *args, **kwargs):
"""
Send a message to a Redis queu.
@ -32,7 +32,6 @@ class RedisPlugin(Plugin):
redis = Redis(*args, **kwargs)
redis.rpush(queue, msg)
return Response(output={'state': 'ok'})
# vim:sw=4:ts=4:et:

View File

@ -1,9 +1,7 @@
import json
import serial
from platypush.message.response import Response
from .. import Plugin
from platypush.plugins import Plugin, action
class SerialPlugin(Plugin):
@ -60,6 +58,7 @@ class SerialPlugin(Plugin):
return output.decode().strip()
@action
def get_data(self):
"""
Reads JSON data from the serial device and returns it as a message
@ -78,7 +77,7 @@ class SerialPlugin(Plugin):
self.logger.warning('Invalid JSON message from {}: {}'.
format(self.device, data))
return Response(output=data)
return data
# vim:sw=4:ts=4:et:

View File

@ -1,14 +1,15 @@
import subprocess
from platypush.message.response import Response
from platypush.plugins import Plugin, action
from .. import Plugin
class ShellPlugin(Plugin):
"""
Plugin to run custom shell commands.
"""
@action
def exec(self, cmd):
"""
Execute a command.
@ -19,16 +20,12 @@ class ShellPlugin(Plugin):
:returns: A response object where the ``output`` field will contain the command output as a string, and the ``errors`` field will contain whatever was sent to stderr.
"""
output = None
errors = []
try:
output = subprocess.check_output(
return subprocess.check_output(
cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8')
except subprocess.CalledProcessError as e:
errors = [e.output.decode('utf-8')]
raise RuntimeError(e.output.decode('utf-8'))
return Response(output=output, errors=errors)
# vim:sw=4:ts=4:et:

View File

@ -1,22 +1,29 @@
from .. import Plugin
from platypush.plugins import Plugin, action
class SwitchPlugin(Plugin):
"""
Abstract class for interacting with switch devices
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@action
def on(self, args):
""" Turn the device on """
raise NotImplementedError()
@action
def off(self, args):
""" Turn the device off """
raise NotImplementedError()
@action
def toggle(self, args):
""" Toggle the device status (on/off) """
raise NotImplementedError()
@action
def status(self):
""" Get the device state """
raise NotImplementedError()

View File

@ -4,9 +4,8 @@ import time
from bluetooth.ble import DiscoveryService, GATTRequester
from platypush.message.response import Response
from .. import SwitchPlugin
from platypush.plugins import action
from platypush.plugins.switch import SwitchPlugin
class Scanner(object):
service_uuid = '1bc5d5a5-0200b89f-e6114d22-000da2cb'
@ -112,6 +111,7 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
:type devices: dict
"""
super().__init__(*args, **kwargs)
self.bt_interface = bt_interface
self.connect_timeout = connect_timeout if connect_timeout else 5
self.scan_timeout = scan_timeout if scan_timeout else 2
@ -119,15 +119,12 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
def _run(self, device, command=None):
output = None
errors = []
try:
# XXX this requires sudo and it's executed in its own process
# because the Switchbot plugin requires root privileges to send
# raw bluetooth messages on the interface. Make sure that the user
# that runs platypush has the right permissions to run this with sudo
output = subprocess.check_output((
return subprocess.check_output((
'sudo python3 -m platypush.plugins.switch.switchbot ' +
'--device {} ' +
('--interface {} '.format(self.bt_interface) if self.bt_interface else '') +
@ -135,11 +132,10 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
('--{} '.format(command) if command else '')).format(device),
stderr=subprocess.STDOUT, shell=True).decode('utf-8')
except subprocess.CalledProcessError as e:
errors = [e.output.decode('utf-8')]
return Response(output=output, errors=errors)
raise RuntimeError(e.output.decode('utf-8'))
@action
def press(self, device):
"""
Send a press button command to a device
@ -149,6 +145,7 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
"""
return self._run(device)
@action
def on(self, device):
"""
Send a press-on button command to a device
@ -158,6 +155,7 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
"""
return self._run(device, 'on')
@action
def off(self, device):
"""
Send a press-off button command to a device
@ -167,25 +165,17 @@ class SwitchSwitchbotPlugin(SwitchPlugin):
"""
return self._run(device, 'off')
@action
def scan(self):
""" Scan for available Switchbot devices nearby """
output = None
errors = []
try:
print('sudo python3 -m platypush.plugins.switch.switchbot --scan ' +
('--interface {} '.format(self.bt_interface) if self.bt_interface else '') +
('--scan-timeout {} '.format(self.scan_timeout) if self.scan_timeout else ''))
output = subprocess.check_output(
return subprocess.check_output(
'sudo python3 -m platypush.plugins.switch.switchbot --scan ' +
('--interface {} '.format(self.bt_interface) if self.bt_interface else '') +
('--scan-timeout {} '.format(self.scan_timeout) if self.scan_timeout else ''),
stderr=subprocess.STDOUT, shell=True).decode('utf-8')
except subprocess.CalledProcessError as e:
errors = [e.output.decode('utf-8')]
return Response(output=output, errors=errors)
raise RuntimeError(e.output.decode('utf-8'))
# vim:sw=4:ts=4:et:

View File

@ -1,6 +1,6 @@
from pyHS100 import Discover
from platypush.message.response import Response
from platypush.plugins import action
from platypush.plugins.switch import SwitchPlugin
@ -17,6 +17,8 @@ class SwitchTplinkPlugin(SwitchPlugin):
_ip_to_dev = {}
_alias_to_dev = {}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def _scan(self):
devices = Discover.discover()
@ -46,6 +48,7 @@ class SwitchTplinkPlugin(SwitchPlugin):
raise RuntimeError('Device {} not found'.format(device))
@action
def status(self):
"""
:returns: The available device over the network as a
@ -61,8 +64,9 @@ class SwitchTplinkPlugin(SwitchPlugin):
} for (ip, dev) in self._scan().items()
} }
return Response(output=devices)
return devices
@action
def on(self, device):
"""
Turn on a device
@ -73,9 +77,10 @@ class SwitchTplinkPlugin(SwitchPlugin):
device = self._get_device(device)
device.turn_on()
return Response(output={'status':'on'})
return {'status':'on'}
@action
def off(self, device):
"""
Turn off a device
@ -86,9 +91,10 @@ class SwitchTplinkPlugin(SwitchPlugin):
device = self._get_device(device)
device.turn_off()
return Response(output={'status':'off'})
return {'status':'off'}
@action
def toggle(self, device):
"""
Toggle the state of a device (on/off)
@ -104,7 +110,7 @@ class SwitchTplinkPlugin(SwitchPlugin):
else:
device.turn_on()
return Response(output={'status': 'off' if device.is_off else 'on'})
return {'status': 'off' if device.is_off else 'on'}
# vim:sw=4:ts=4:et:

View File

@ -1,9 +1,9 @@
import json
from ouimeaux.environment import Environment, UnknownDevice
from platypush.message.response import Response
from platypush.plugins import action
from platypush.plugins.switch import SwitchPlugin
from .. import SwitchPlugin
class SwitchWemoPlugin(SwitchPlugin):
"""
@ -20,8 +20,8 @@ class SwitchWemoPlugin(SwitchPlugin):
:param discovery_seconds: Discovery time when scanning for devices (default: 3)
:type discovery_seconds: int
"""
super().__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.discovery_seconds=discovery_seconds
self.env = Environment()
self.env.start()
@ -33,6 +33,7 @@ class SwitchWemoPlugin(SwitchPlugin):
self.env.discover(seconds=self.discovery_seconds)
self.devices = self.env.devices
@action
def get_devices(self):
"""
Get the list of available devices
@ -57,8 +58,8 @@ class SwitchWemoPlugin(SwitchPlugin):
}
"""
self.refresh_devices()
return Response(
output = { 'devices': [
return {
'devices': [
{
'host': dev.host,
'name': dev.name,
@ -67,8 +68,8 @@ class SwitchWemoPlugin(SwitchPlugin):
'serialnumber': dev.serialnumber,
}
for (name, dev) in self.devices.items()
] }
)
]
}
def _exec(self, method, device, *args, **kwargs):
if device not in self.devices:
@ -81,9 +82,9 @@ class SwitchWemoPlugin(SwitchPlugin):
dev = self.devices[device]
getattr(dev, method)(*args, **kwargs)
resp = {'device': device, 'state': dev.get_state()}
return Response(output=json.dumps(resp))
return {'device': device, 'state': dev.get_state()}
@action
def on(self, device):
"""
Turn a switch on
@ -93,6 +94,7 @@ class SwitchWemoPlugin(SwitchPlugin):
"""
return self._exec('on', device)
@action
def off(self, device):
"""
Turn a switch off
@ -102,6 +104,7 @@ class SwitchWemoPlugin(SwitchPlugin):
"""
return self._exec('off', device)
@action
def toggle(self, device):
"""
Toggle the state of a switch (on/off)

View File

@ -2,8 +2,7 @@ import subprocess
import urllib.parse
from platypush.message.response import Response
from .. import Plugin
from platypush.plugins import Plugin, action
class TtsPlugin(Plugin):
"""
@ -18,6 +17,7 @@ class TtsPlugin(Plugin):
super().__init__()
self.lang=lang
@action
def say(self, phrase, lang=None):
"""
Say a phrase
@ -41,12 +41,10 @@ class TtsPlugin(Plugin):
}))]
try:
output = subprocess.check_output(
return subprocess.check_output(
cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8')
except subprocess.CalledProcessError as e:
errors = [e.output.decode('utf-8')]
return Response(output=output, errors=errors)
raise RuntimeError(e.output.decode('utf-8'))
# vim:sw=4:ts=4:et:

View File

@ -1,5 +1,4 @@
from platypush.message.response import Response
from platypush.plugins import Plugin
from platypush.plugins import Plugin, action
class VariablePlugin(Plugin):
@ -12,6 +11,7 @@ class VariablePlugin(Plugin):
super().__init__(*args, **kwargs)
self._variables = {}
@action
def get(self, name, default_value=None):
"""
Get the value of a variable by name.
@ -24,8 +24,9 @@ class VariablePlugin(Plugin):
:returns: A map in the format ``{"<name>":"<value>"}``
"""
return Response(output={name: self._variables.get(name, default_value)})
return {name: self._variables.get(name, default_value)}
@action
def set(self, **kwargs):
"""
Set a variable or a set of variables.
@ -35,8 +36,9 @@ class VariablePlugin(Plugin):
for (name, value) in kwargs.items():
self._variables[name] = value
return Response(output=kwargs)
return kwargs
@action
def unset(self, name):
"""
Unset a variable by name if it's set
@ -46,9 +48,6 @@ class VariablePlugin(Plugin):
"""
if name in self._variables:
del self._variables[name]
return Response(output={'status':'ok'})
else:
return Response(output={'status':'not_found'})
# vim:sw=4:ts=4:et:

View File

@ -12,11 +12,11 @@ from omxplayer import OMXPlayer
from platypush.context import get_backend
from platypush.plugins.media import PlayerState
from platypush.message.response import Response
from platypush.message.event.video import VideoPlayEvent, VideoPauseEvent, \
VideoStopEvent, NewPlayingVideoEvent
from .. import Plugin
from platypush.plugins import Plugin, action
class VideoOmxplayerPlugin(Plugin):
"""
@ -83,6 +83,7 @@ class VideoOmxplayerPlugin(Plugin):
self.videos_queue = []
self.torrent_ports = torrent_ports if torrent_ports else self.default_torrent_ports
@action
def play(self, resource):
"""
Play a resource.
@ -105,9 +106,7 @@ class VideoOmxplayerPlugin(Plugin):
self.videos_queue = resources
resource = self.videos_queue.pop(0)
else:
error = 'Unable to download torrent {}'.format(resource)
self.logger.warning(error)
return Response(errors=[error])
raise RuntimeError('Unable to download torrent {}'.format(resource))
self.logger.info('Playing {}'.format(resource))
@ -130,10 +129,12 @@ class VideoOmxplayerPlugin(Plugin):
return self.status()
@action
def pause(self):
""" Pause the playback """
if self.player: self.player.play_pause()
@action
def stop(self):
""" Stop the playback """
if self.player:
@ -143,30 +144,35 @@ class VideoOmxplayerPlugin(Plugin):
return self.status()
@action
def voldown(self):
""" Volume down by 10% """
if self.player:
self.player.set_volume(max(-6000, self.player.volume()-1000))
return self.status()
@action
def volup(self):
""" Volume up by 10% """
if self.player:
self.player.set_volume(min(0, self.player.volume()+1000))
return self.status()
@action
def back(self):
""" Back by 30 seconds """
if self.player:
self.player.seek(-30)
return self.status()
@action
def forward(self):
""" Forward by 30 seconds """
if self.player:
self.player.seek(+30)
return self.status()
@action
def next(self):
""" Play the next track/video """
if self.player:
@ -176,19 +182,19 @@ class VideoOmxplayerPlugin(Plugin):
video = self.videos_queue.pop(0)
return self.play(video)
return Response(output={'status': 'no media'}, errors = [])
@action
def hide_subtitles(self):
""" Hide the subtitles """
if self.player: self.player.hide_subtitles()
return self.status()
@action
def hide_video(self):
""" Hide the video """
if self.player: self.player.hide_video()
return self.status()
@action
def is_playing(self):
"""
:returns: True if it's playing, False otherwise
@ -197,6 +203,7 @@ class VideoOmxplayerPlugin(Plugin):
if self.player: return self.player.is_playing()
else: return False
@action
def load(self, resource, pause=False):
"""
Load a resource/video in the player.
@ -208,21 +215,26 @@ class VideoOmxplayerPlugin(Plugin):
if self.player: self.player.load(resource, pause)
return self.status()
@action
def metadata(self):
""" Get the metadata of the current video """
if self.player: return Response(output=self.player.metadata())
if self.player:
return self.player.metadata()
return self.status()
@action
def mute(self):
""" Mute the player """
if self.player: self.player.mute()
return self.status()
@action
def unmute(self):
""" Unmute the player """
if self.player: self.player.unmute()
return self.status()
@action
def seek(self, relative_position):
"""
Seek backward/forward by the specified number of seconds
@ -234,6 +246,7 @@ class VideoOmxplayerPlugin(Plugin):
if self.player: self.player.seek(relative_position)
return self.status()
@action
def set_position(self, position):
"""
Seek backward/forward to the specified absolute position
@ -245,6 +258,7 @@ class VideoOmxplayerPlugin(Plugin):
if self.player: self.player.set_seek(position)
return self.status()
@action
def set_volume(self, volume):
"""
Set the volume
@ -258,6 +272,7 @@ class VideoOmxplayerPlugin(Plugin):
if self.player: self.player.set_volume(volume)
return self.status()
@action
def status(self):
"""
Get the current player state.
@ -285,7 +300,7 @@ class VideoOmxplayerPlugin(Plugin):
elif state == 'stopped': state = PlayerState.STOP.value
elif state == 'paused': state = PlayerState.PAUSE.value
return Response(output=json.dumps({
return {
'source': self.player.get_source(),
'state': state,
'volume': self.player.volume(),
@ -293,12 +308,13 @@ class VideoOmxplayerPlugin(Plugin):
'duration': self.player.duration(),
'width': self.player.width(),
'height': self.player.height(),
}))
}
else:
return Response(output=json.dumps({
return {
'state': PlayerState.STOP.value
}))
}
@action
def send_message(self, msg):
try:
redis = get_backend('redis')
@ -311,16 +327,19 @@ class VideoOmxplayerPlugin(Plugin):
redis.send_message(msg)
@action
def on_play(self):
def _f(player):
self.send_message(VideoPlayEvent(video=self.player.get_source()))
return _f
@action
def on_pause(self):
def _f(player):
self.send_message(VideoPauseEvent(video=self.player.get_source()))
return _f
@action
def on_stop(self):
def _f(player):
self.send_message(VideoStopEvent())
@ -335,6 +354,7 @@ class VideoOmxplayerPlugin(Plugin):
self.player.pauseEvent += self.on_pause()
self.player.stopEvent += self.on_stop()
@action
def search(self, query, types=None, queue_results=False, autoplay=False):
"""
Perform a video search.
@ -376,7 +396,7 @@ class VideoOmxplayerPlugin(Plugin):
elif autoplay:
self.play(results[0]['url'])
return Response(output=results)
return results
@classmethod
def _is_video_file(cls, filename):
@ -388,6 +408,7 @@ class VideoOmxplayerPlugin(Plugin):
return is_video
@action
def file_search(self, query):
results = []
query_tokens = [_.lower() for _ in re.split('\s+', query.strip())]
@ -413,8 +434,9 @@ class VideoOmxplayerPlugin(Plugin):
'title': f,
})
return Response(output=results)
return results
@action
def youtube_search(self, query):
self.logger.info('Searching YouTube for "{}"'.format(query))
@ -439,7 +461,7 @@ class VideoOmxplayerPlugin(Plugin):
self.logger.info('{} YouTube video results for the search query "{}"'
.format(len(results), query))
return Response(output=results)
return results
@classmethod
@ -476,8 +498,9 @@ class VideoOmxplayerPlugin(Plugin):
for _ in json.loads(request.read())['MovieList']
]
return Response(output=results)
return results
@action
def download_torrent(self, magnet):
"""
Download a torrent to ``download_dir`` by Magnet URI
@ -538,9 +561,10 @@ class VideoOmxplayerPlugin(Plugin):
time.sleep(5)
return Response(output=files)
return files
@action
def get_torrent_state(self):
return self.torrent_state

View File

@ -3,10 +3,8 @@ import urllib3
import urllib.request
import urllib.parse
from platypush.plugins import Plugin, action
from platypush.plugins.media import PlayerState
from platypush.message.response import Response
from .. import Plugin
class VideoTorrentcastPlugin(Plugin):
def __init__(self, server='localhost', port=9090, *args, **kwargs):
@ -14,6 +12,7 @@ class VideoTorrentcastPlugin(Plugin):
self.port = port
self.state = PlayerState.STOP.value
@action
def play(self, url):
request = urllib.request.urlopen(
'http://{}:{}/play/'.format(self.server, self.port),
@ -23,24 +22,27 @@ class VideoTorrentcastPlugin(Plugin):
)
self.state = PlayerState.PLAY.value
return Response(output=request.read())
return request.read()
@action
def pause(self):
http = urllib3.PoolManager()
request = http.request('POST',
'http://{}:{}/pause/'.format(self.server, self.port))
self.state = PlayerState.PAUSE.value
return Response(output=request.read())
return request.read()
@action
def stop(self):
http = urllib3.PoolManager()
request = http.request('POST',
'http://{}:{}/stop/'.format(self.server, self.port))
self.state = PlayerState.STOP.value
return Response(output=request.read())
return request.read()
@action
def search(self, query):
request = urllib.request.urlopen(urllib.request.Request(
'https://api.apidomain.info/list?' + urllib.parse.urlencode({
@ -56,8 +58,9 @@ class VideoTorrentcastPlugin(Plugin):
)
results = json.loads(request.read())
return Response(output=results)
return results
@action
def search_and_play(self, query):
response = self.search(query)
if not response.output['MovieList']:
@ -71,13 +74,20 @@ class VideoTorrentcastPlugin(Plugin):
return self.play(magnet)
def voldown(self): return Response(output='Unsupported method')
def volup(self): return Response(output='Unsupported method')
def back(self): return Response(output='Unsupported method')
def forward(self): return Response(output='Unsupported method')
@action
def voldown(self): raise NotImplementedError()
def status(self):
return Response(output={ 'state': self.state })
@action
def volup(self): raise NotImplementedError()
@action
def back(self): raise NotImplementedError()
@action
def forward(self): raise NotImplementedError()
@action
def status(self): return { 'state': self.state }
# vim:sw=4:ts=4:et:

View File

@ -1,4 +1,4 @@
from platypush.message.response import Response
from platypush.plugins import action
from platypush.plugins.http.request import HttpRequestPlugin
@ -46,6 +46,7 @@ class WeatherForecastPlugin(HttpRequestPlugin):
format(self.darksky_token, (lat or self.lat), (long or self.long),
self.units)
@action
def get_current_weather(self, lat=None, long=None, **kwargs):
"""
Get the current weather.
@ -83,8 +84,9 @@ class WeatherForecastPlugin(HttpRequestPlugin):
"""
response = self.get(self._get_url(lat, long))
return Response(output=response.output['currently'])
return response.output['currently']
@action
def get_hourly_forecast(self, lat=None, long=None, **kwargs):
"""
Get the hourly forecast.
@ -146,8 +148,9 @@ class WeatherForecastPlugin(HttpRequestPlugin):
"""
response = self.get(self._get_url(lat, long))
return Response(output=response.output['hourly'])
return response.output['hourly']
@action
def get_daily_forecast(self, lat=None, long=None, **kwargs):
"""
Get the daily forecast.
@ -250,7 +253,7 @@ class WeatherForecastPlugin(HttpRequestPlugin):
"""
response = self.get(self._get_url(lat, long))
return Response(output=response.output['daily'])
return response.output['daily']
# vim:sw=4:ts=4:et:

View File

@ -1,6 +1,8 @@
import ast
import errno
import hashlib
import importlib
import inspect
import logging
import os
import signal
@ -67,8 +69,32 @@ def clear_timeout():
def get_hash(s):
""" Get the SHA256 hash hexdigest of a string input """
return hashlib.sha256(s.encode('utf-8')).hexdigest()
def get_decorators(cls):
target = cls
decorators = {}
def visit_FunctionDef(node):
# decorators[node.name] = []
for n in node.decorator_list:
name = ''
if isinstance(n, ast.Call):
name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id
else:
name = n.attr if isinstance(n, ast.Attribute) else n.id
decorators[name] = decorators.get(name, [])
# decorators[node.name].append(name)
decorators[name].append(node.name)
node_iter = ast.NodeVisitor()
node_iter.visit_FunctionDef = visit_FunctionDef
node_iter.visit(ast.parse(inspect.getsource(target)))
return decorators
# vim:sw=4:ts=4:et: