2017-10-29 05:17:42 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
2017-11-03 02:32:32 +01:00
|
|
|
import functools
|
2017-10-31 10:24:15 +01:00
|
|
|
import importlib
|
2017-10-29 05:17:42 +01:00
|
|
|
import os
|
|
|
|
import logging
|
|
|
|
import json
|
2017-10-30 02:54:16 +01:00
|
|
|
import socket
|
2017-10-29 05:17:42 +01:00
|
|
|
import subprocess
|
2017-10-30 02:54:16 +01:00
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import websocket
|
2017-11-03 01:56:27 +01:00
|
|
|
import yaml
|
2017-10-29 05:17:42 +01:00
|
|
|
|
2017-10-31 10:24:15 +01:00
|
|
|
from threading import Thread
|
2017-10-30 02:54:16 +01:00
|
|
|
from getopt import getopt
|
2017-10-29 05:17:42 +01:00
|
|
|
|
2017-11-03 01:56:27 +01:00
|
|
|
__author__ = 'Fabio Manganiello <info@fabiomanganiello.com>'
|
|
|
|
|
|
|
|
#-----------#
|
|
|
|
|
|
|
|
curdir = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
lib_dir = curdir + os.sep + 'lib'
|
2017-10-31 10:24:15 +01:00
|
|
|
sys.path.insert(0, lib_dir)
|
|
|
|
|
2017-11-03 01:56:27 +01:00
|
|
|
config_file = curdir + os.sep + 'config.yaml'
|
|
|
|
config = {}
|
|
|
|
with open(config_file,'r') as f:
|
|
|
|
config = yaml.load(f)
|
2017-10-29 05:17:42 +01:00
|
|
|
|
2017-11-03 11:31:19 +01:00
|
|
|
API_KEY = config['pushbullet']['token']
|
2017-11-03 01:56:27 +01:00
|
|
|
DEVICE_ID = config['device_id'] \
|
|
|
|
if 'device_id' in config else socket.gethostname()
|
|
|
|
|
|
|
|
DEBUG = config['debug'] if 'debug' in config else False
|
2017-10-29 05:17:42 +01:00
|
|
|
|
2017-11-03 02:42:56 +01:00
|
|
|
modules = {}
|
|
|
|
plugins = {}
|
|
|
|
|
2017-10-29 05:17:42 +01:00
|
|
|
|
2017-10-30 02:54:16 +01:00
|
|
|
def on_open(ws):
|
|
|
|
logging.info('Connection opened')
|
|
|
|
|
|
|
|
|
|
|
|
def on_close(ws):
|
|
|
|
logging.info('Connection closed')
|
2017-10-29 05:17:42 +01:00
|
|
|
|
|
|
|
|
2017-10-30 02:54:16 +01:00
|
|
|
def on_error(ws, error):
|
|
|
|
logging.error(error)
|
|
|
|
|
|
|
|
|
2017-11-03 04:08:47 +01:00
|
|
|
def _init_plugin(plugin, reload=False):
|
|
|
|
module_name = 'plugins.{}'.format(plugin)
|
|
|
|
if module_name not in modules or reload:
|
2017-11-03 02:42:56 +01:00
|
|
|
try:
|
2017-11-03 04:08:47 +01:00
|
|
|
modules[module_name] = importlib.import_module(module_name)
|
2017-11-03 02:42:56 +01:00
|
|
|
except ModuleNotFoundError as e:
|
2017-11-03 04:08:47 +01:00
|
|
|
logging.warn('No such plugin: {}'.format(plugin))
|
|
|
|
raise RuntimeError(e)
|
2017-10-31 10:24:15 +01:00
|
|
|
|
2017-11-03 04:36:24 +01:00
|
|
|
# e.g. plugins.music.mpd main class: MusicMpdPlugin
|
2017-11-03 02:42:56 +01:00
|
|
|
cls_name = functools.reduce(
|
|
|
|
lambda a,b: a.title() + b.title(),
|
2017-11-03 04:08:47 +01:00
|
|
|
(plugin.title().split('.'))
|
2017-11-03 02:42:56 +01:00
|
|
|
) + 'Plugin'
|
|
|
|
|
2017-11-03 04:08:47 +01:00
|
|
|
if cls_name not in plugins or reload:
|
|
|
|
try:
|
|
|
|
plugins[cls_name] = getattr(modules[module_name], cls_name)()
|
|
|
|
except AttributeError as e:
|
|
|
|
logging.warn('No such class in {}: {}'.format(
|
|
|
|
module_name, cls_name))
|
|
|
|
raise RuntimeError(e)
|
|
|
|
|
|
|
|
return plugins[cls_name]
|
|
|
|
|
2017-11-03 02:42:56 +01:00
|
|
|
|
2017-11-03 04:08:47 +01:00
|
|
|
def _exec_func(body, retry=True):
|
|
|
|
try:
|
|
|
|
logging.info('Received push addressed to me: {}'.format(body))
|
|
|
|
args = body['args'] if 'args' in body else {}
|
|
|
|
if 'plugin' not in body:
|
|
|
|
logging.warn('No plugin specified')
|
|
|
|
return
|
|
|
|
|
|
|
|
plugin_name = body['plugin']
|
|
|
|
|
|
|
|
try:
|
|
|
|
plugin = _init_plugin(plugin_name)
|
|
|
|
except RuntimeError as e: # Module/class not found
|
|
|
|
return
|
2017-10-31 10:24:15 +01:00
|
|
|
|
2017-11-03 04:08:47 +01:00
|
|
|
ret = plugin.run(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:
|
|
|
|
logging.info('Reloading plugin {} and retrying'.format(plugin_name))
|
|
|
|
_init_plugin(plugin_name, reload=True)
|
|
|
|
_exec_func(body, retry=False)
|
2017-10-31 10:24:15 +01:00
|
|
|
|
|
|
|
|
2017-10-30 02:54:16 +01:00
|
|
|
def _on_push(ws, data):
|
|
|
|
data = json.loads(data)
|
|
|
|
if data['type'] == 'tickle' and data['subtype'] == 'push':
|
|
|
|
logging.debug('Received push tickle')
|
|
|
|
return
|
2017-10-29 05:17:42 +01:00
|
|
|
|
2017-10-30 02:54:16 +01:00
|
|
|
if data['type'] != 'push':
|
2017-10-29 05:17:42 +01:00
|
|
|
return # Not a push notification
|
|
|
|
|
|
|
|
push = data['push']
|
|
|
|
logging.debug('Received push: {}'.format(push))
|
|
|
|
|
2017-10-31 10:24:15 +01:00
|
|
|
if 'body' not in push:
|
|
|
|
return
|
|
|
|
|
2017-10-29 20:45:19 +01:00
|
|
|
body = push['body']
|
2017-10-29 05:17:42 +01:00
|
|
|
try:
|
2017-10-29 20:45:19 +01:00
|
|
|
body = json.loads(body)
|
2017-10-29 05:17:42 +01:00
|
|
|
except ValueError as e:
|
|
|
|
return
|
|
|
|
|
2017-10-30 02:54:16 +01:00
|
|
|
if 'target' not in body or body['target'] != DEVICE_ID:
|
2017-10-29 05:17:42 +01:00
|
|
|
return # Not for me
|
|
|
|
|
2017-10-31 10:24:15 +01:00
|
|
|
if 'plugin' not in body:
|
|
|
|
return # No plugin specified
|
2017-10-29 05:17:42 +01:00
|
|
|
|
2017-10-31 10:24:15 +01:00
|
|
|
thread = Thread(target=_exec_func, args=(body,))
|
|
|
|
thread.start()
|
2017-10-29 05:17:42 +01:00
|
|
|
|
2017-10-30 02:54:16 +01:00
|
|
|
def on_push(ws, data):
|
2017-10-29 05:17:42 +01:00
|
|
|
try:
|
2017-10-30 02:54:16 +01:00
|
|
|
_on_push(ws, data)
|
2017-10-31 10:24:15 +01:00
|
|
|
except Exception as e:
|
|
|
|
on_error(ws, e)
|
2017-10-30 02:54:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2017-11-03 01:56:27 +01:00
|
|
|
DEBUG = False
|
|
|
|
config_file = curdir + os.sep + 'config.yaml'
|
2017-10-30 02:54:16 +01:00
|
|
|
|
|
|
|
optlist, args = getopt(sys.argv[1:], 'vh')
|
|
|
|
for opt, arg in optlist:
|
2017-11-03 01:56:27 +01:00
|
|
|
if opt == '-c':
|
|
|
|
config_file = arg
|
2017-10-30 02:54:16 +01:00
|
|
|
if opt == '-v':
|
|
|
|
DEBUG = True
|
|
|
|
elif opt == '-h':
|
|
|
|
print('''
|
2017-11-03 01:56:27 +01:00
|
|
|
Usage: {} [-v] [-h] [-c <config_file>]
|
2017-10-30 02:54:16 +01:00
|
|
|
-v Enable debug mode
|
|
|
|
-h Show this help
|
2017-11-03 01:56:27 +01:00
|
|
|
-c Path to the configuration file (default: ./config.yaml)
|
2017-10-30 02:54:16 +01:00
|
|
|
'''.format(sys.argv[0]))
|
2017-10-30 02:59:35 +01:00
|
|
|
return
|
2017-10-30 02:54:16 +01:00
|
|
|
|
2017-11-03 01:56:27 +01:00
|
|
|
config = {}
|
|
|
|
with open(config_file,'r') as f:
|
|
|
|
config = yaml.load(f)
|
|
|
|
|
|
|
|
API_KEY = config['pushbullet_token']
|
|
|
|
DEVICE_ID = config['device_id'] \
|
|
|
|
if 'device_id' in config else socket.gethostname()
|
|
|
|
|
|
|
|
if 'debug' in config:
|
|
|
|
DEBUG = config['debug']
|
|
|
|
|
2017-10-30 02:54:16 +01:00
|
|
|
if DEBUG:
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
websocket.enableTrace(True)
|
|
|
|
else:
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
|
|
|
ws = websocket.WebSocketApp('wss://stream.pushbullet.com/websocket/' +
|
|
|
|
API_KEY,
|
|
|
|
on_message = on_push,
|
|
|
|
on_error = on_error,
|
|
|
|
on_close = on_close)
|
|
|
|
ws.on_open = on_open
|
|
|
|
ws.run_forever()
|
2017-10-29 05:17:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|
2017-10-29 20:45:19 +01:00
|
|
|
|