platypush/runbullet/__init__.py
2017-11-09 01:43:17 +01:00

234 lines
5.5 KiB
Python

#!/usr/bin/env python
import functools
import importlib
import os
import logging
import json
import socket
import subprocess
import sys
import time
import websocket
import yaml
from threading import Thread
from getopt import getopt
__author__ = 'Fabio Manganiello <info@fabiomanganiello.com>'
#-----------#
config = {}
modules = {}
wrkdir = os.path.dirname(os.path.realpath(__file__))
def on_open(ws):
logging.info('Connection opened')
def on_close(ws):
logging.info('Connection closed')
def on_error(ws, error):
logging.error(error)
def _init_plugin(plugin_name, reload=False):
global modules
global config
if plugin_name in modules and not reload:
return modules[plugin_name]
try:
logging.warn(__package__ + '.plugins.' + plugin_name)
module = importlib.import_module(__package__ + '.plugins.' + plugin_name)
except ModuleNotFoundError as e:
logging.warn('No such plugin: {}'.format(plugin_name))
raise RuntimeError(e)
# e.g. plugins.music.mpd main class: MusicMpdPlugin
cls_name = functools.reduce(
lambda a,b: a.title() + b.title(),
(plugin_name.title().split('.'))
) + 'Plugin'
plugin_conf = config[plugin_name] if plugin_name in config else {}
try:
plugin = getattr(module, cls_name)(plugin_conf)
modules[plugin_name] = plugin
except AttributeError as e:
logging.warn('No such class in {}: {}'.format(
plugin_name, cls_name))
raise RuntimeError(e)
return plugin
def _exec_func(args, retry=True):
args = json.loads(args) \
if isinstance(args, str) \
else args
if 'action' not in args:
logging.warn('No action specified')
return
if 'target' in args:
args.pop('target')
action = args.pop('action')
tokens = action.split('.')
module_name = str.join('.', tokens[:-1])
method_name = tokens[-1:][0]
try:
plugin = _init_plugin(module_name)
except RuntimeError as e: # Module/class not found
return
try:
ret = plugin.run(method=method_name, **args)
out = None
err = None
if isinstance(ret, list):
out = ret[0]
err = ret[1] if len(ret) > 1 else None
elif ret is not None:
out = ret
if out:
logging.info('Command output: {}'.format(out))
if err:
logging.warn('Command error: {}'.format(err))
except Exception as e:
logging.exception(e)
if retry:
# Put the action back where it was before retrying
args['action'] = action
logging.info('Reloading plugin {} and retrying'.format(module_name))
_init_plugin(module_name, reload=True)
_exec_func(args, retry=False)
def _on_push(ws, data):
global config
data = json.loads(data)
if data['type'] == 'tickle' and data['subtype'] == 'push':
logging.debug('Received push tickle')
return
if data['type'] != 'push':
return # Not a push notification
push = data['push']
logging.debug('Received push: {}'.format(push))
if 'body' not in push:
return
body = push['body']
try:
body = json.loads(body)
except ValueError as e:
return
if 'target' not in body or body['target'] != config['device_id']:
return # Not for me
logging.info('Received push addressed to me: {}'.format(body))
thread = Thread(target=_exec_func, args=(body,))
thread.start()
def on_push(ws, data):
try:
_on_push(ws, data)
except Exception as e:
on_error(ws, e)
def parse_config_file(config_file=None):
global config
if config_file:
locations = [config_file]
else:
locations = [
# ./config.yaml
os.path.join(wrkdir, 'config.yaml'),
# ~/.config/runbullet/config.yaml
os.path.join(os.environ['HOME'], '.config', 'runbullet', 'config.yaml'),
# /etc/runbullet/config.yaml
os.path.join(os.sep, 'etc', 'runbullet', 'config.yaml'),
]
for loc in locations:
try:
with open(loc,'r') as f:
config = yaml.load(f)
except FileNotFoundError as e:
pass
return config
def main():
DEBUG = False
config_file = None
plugins_dir = os.path.join(wrkdir, 'plugins')
sys.path.insert(0, plugins_dir)
optlist, args = getopt(sys.argv[1:], 'vh')
for opt, arg in optlist:
if opt == '-c':
config_file = arg
if opt == '-v':
DEBUG = True
elif opt == '-h':
print('''
Usage: {} [-v] [-h] [-c <config_file>]
-v Enable debug mode
-h Show this help
-c Path to the configuration file (default: ./config.yaml)
'''.format(sys.argv[0]))
return
config = parse_config_file(config_file)
if 'device_id' not in config:
config['device_id'] = socket.gethostname()
if 'debug' in config:
DEBUG = config['debug']
if DEBUG:
logging.basicConfig(level=logging.DEBUG)
websocket.enableTrace(True)
else:
logging.basicConfig(level=logging.INFO)
ws = websocket.WebSocketApp('wss://stream.pushbullet.com/websocket/' +
config['pushbullet']['token'],
on_message = on_push,
on_error = on_error,
on_close = on_close)
ws.on_open = on_open
ws.run_forever()
if __name__ == '__main__':
main()
# vim:sw=4:ts=4:et: