From 99a93012cec113d5a0ef52a20d9ac913cdd88745 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Fri, 3 Nov 2017 15:06:29 +0100 Subject: [PATCH] Proper release with setuptools support --- README.md | 17 +++++++ runbullet/__init__.py | 53 ++++++++++++++------- runbullet/{pusher.py => bin/pusher} | 14 ++---- runbullet/bin/runbullet | 8 ++++ runbullet/lib/plugins/__init__.py | 18 ++----- runbullet/lib/plugins/music/__init__.py | 42 ++++++++++++++-- runbullet/lib/plugins/music/mpd/__init__.py | 18 +++++++ setup.py | 36 ++++++++++++-- 8 files changed, 159 insertions(+), 47 deletions(-) rename runbullet/{pusher.py => bin/pusher} (86%) mode change 100644 => 100755 create mode 100755 runbullet/bin/runbullet diff --git a/README.md b/README.md index 7f02bce5..e32277fc 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,20 @@ Runbullet Execute any command or custom complex logic on your devices, wherever they are, using your PushBullet account. +Installation +------------ + +```shell +pip install runbullet +``` + +Configuration +------------- + +Copy /etc/runbullet/config.example.yaml to /etc/runbullet/config.yaml (system-wise settings) or ~/.config/runbullet/config.yaml (user-wise settings). + +Edit the file to include: + +* Your PushBullet access token (create one [here](https://www.pushbullet.com/#settings/account)); +* The name of the (virtual) PushBullet device used to listen for events (create one [here](https://www.pushbullet.com/#devices)). + diff --git a/runbullet/__init__.py b/runbullet/__init__.py index b533acff..97d0d789 100644 --- a/runbullet/__init__.py +++ b/runbullet/__init__.py @@ -23,9 +23,9 @@ curdir = os.path.dirname(os.path.realpath(__file__)) lib_dir = curdir + os.sep + 'lib' sys.path.insert(0, lib_dir) -DEVICE_ID = None modules = {} plugins = {} +config = {} def on_open(ws): @@ -56,8 +56,10 @@ def _init_plugin(plugin, reload=False): ) + 'Plugin' if cls_name not in plugins or reload: + plugin_conf = config[plugin] if plugin in config else {} + try: - plugins[cls_name] = getattr(modules[module_name], cls_name)() + plugins[cls_name] = getattr(modules[module_name], cls_name)(plugin_conf) except AttributeError as e: logging.warn('No such class in {}: {}'.format( module_name, cls_name)) @@ -69,7 +71,7 @@ def _init_plugin(plugin, reload=False): def _exec_func(body, retry=True): try: logging.info('Received push addressed to me: {}'.format(body)) - args = body['args'] if 'args' in body else {} + args = json.loads(body['args']) if 'args' in body else {} if 'plugin' not in body: logging.warn('No plugin specified') return @@ -105,8 +107,6 @@ def _exec_func(body, retry=True): def _on_push(ws, data): - global DEVICE_ID - data = json.loads(data) if data['type'] == 'tickle' and data['subtype'] == 'push': logging.debug('Received push tickle') @@ -127,7 +127,7 @@ def _on_push(ws, data): except ValueError as e: return - if 'target' not in body or body['target'] != DEVICE_ID: + if 'target' not in body or body['target'] != config['device_id']: return # Not for me if 'plugin' not in body: @@ -143,11 +143,35 @@ def on_push(ws, data): on_error(ws, e) -def main(): - global DEVICE_ID +def parse_config_file(config_file=None): + global config + if config_file: + locations = [config_file] + else: + locations = [ + # ./config.yaml + os.path.join(curdir, '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'), + ] + + config = {} + 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 = curdir + os.sep + 'config.yaml' + config_file = None optlist, args = getopt(sys.argv[1:], 'vh') for opt, arg in optlist: @@ -164,13 +188,10 @@ Usage: {} [-v] [-h] [-c ] '''.format(sys.argv[0])) return - config = {} - with open(config_file,'r') as f: - config = yaml.load(f) + config = parse_config_file(config_file) - API_KEY = config['pushbullet']['token'] - DEVICE_ID = config['device_id'] \ - if 'device_id' in config else socket.gethostname() + if 'device_id' not in config: + config['device_id'] = socket.gethostname() if 'debug' in config: DEBUG = config['debug'] @@ -182,7 +203,7 @@ Usage: {} [-v] [-h] [-c ] logging.basicConfig(level=logging.INFO) ws = websocket.WebSocketApp('wss://stream.pushbullet.com/websocket/' + - API_KEY, + config['pushbullet']['token'], on_message = on_push, on_error = on_error, on_close = on_close) diff --git a/runbullet/pusher.py b/runbullet/bin/pusher old mode 100644 new mode 100755 similarity index 86% rename from runbullet/pusher.py rename to runbullet/bin/pusher index 1aaddeb6..9fa8a308 --- a/runbullet/pusher.py +++ b/runbullet/bin/pusher @@ -1,3 +1,5 @@ +#!python + import json import os import sys @@ -5,16 +7,8 @@ import yaml from getopt import getopt from pushbullet import Pushbullet +from runbullet import parse_config_file -def get_config(): - curdir = os.path.dirname(os.path.realpath(__file__)) - config_file = curdir + os.sep + 'config.yaml' - config = {} - - with open(config_file,'r') as f: - config = yaml.load(f) - - return config def print_usage(): print ('''Usage: {} [-h|--help] <-t|--target > <-p|--plugin > payload @@ -25,7 +19,7 @@ def print_usage(): '''.format(sys.argv[0])) def main(): - config = get_config() + config = parse_config_file() API_KEY = config['pushbullet']['token'] pb = Pushbullet(API_KEY) diff --git a/runbullet/bin/runbullet b/runbullet/bin/runbullet new file mode 100755 index 00000000..876f06cc --- /dev/null +++ b/runbullet/bin/runbullet @@ -0,0 +1,8 @@ +#!python + +import runbullet + +runbullet.main() + +# vim:sw=4:ts=4:et: + diff --git a/runbullet/lib/plugins/__init__.py b/runbullet/lib/plugins/__init__.py index 1025a851..eab40f96 100644 --- a/runbullet/lib/plugins/__init__.py +++ b/runbullet/lib/plugins/__init__.py @@ -1,9 +1,10 @@ import os import sys -import yaml class Plugin(object): - def __init__(self): + def __init__(self, config): + self.config = config + for cls in reversed(self.__class__.mro()): if cls is not object: try: @@ -12,19 +13,6 @@ class Plugin(object): pass - def _init(self): - module_dir = os.path.dirname(sys.modules[self.__module__].__file__) - config_file = module_dir + os.sep + 'config.yaml' - - config = {} - - try: - with open(config_file, 'r') as f: - self.config = yaml.load(f) - except FileNotFoundError as e: - pass - - def run(self, args): raise NotImplementedError() diff --git a/runbullet/lib/plugins/music/__init__.py b/runbullet/lib/plugins/music/__init__.py index 27d37437..fc682586 100644 --- a/runbullet/lib/plugins/music/__init__.py +++ b/runbullet/lib/plugins/music/__init__.py @@ -2,11 +2,29 @@ from .. import Plugin class MusicPlugin(Plugin): def run(self, args): - if 'play' in args and self.status()['state'] != 'play': + if 'clear' in args and args['clear']: + self.add(args['clear']) + + if 'playlistadd' in args and args['playlistadd']: + self.playlistadd(args['playlistadd']) + + if 'add' in args and args['add']: + self.add(args['add']) + + if 'next' in args and args['next']: + self.next() + elif 'previous' in args and args['previous']: + self.previous() + + if 'setvol' in args and args['setvol']: + self.setvol(args['setvol']) + + status = self.status() + if 'play' in args and args['play'] and status['state'] != 'play': self.play() - elif 'pause' in args and self.status()['state'] != 'pause': + elif 'pause' in args and args['pause'] and status['state'] != 'pause': self.pause() - elif 'stop' in args: + elif 'stop' in args and args['stop']: self.stop() return self.status() @@ -20,6 +38,24 @@ class MusicPlugin(Plugin): def stop(self): raise NotImplementedError() + def next(self): + raise NotImplementedError() + + def previous(self): + raise NotImplementedError() + + def setvol(self, vol): + raise NotImplementedError() + + def add(self, content): + raise NotImplementedError() + + def playlistadd(self, playlist): + raise NotImplementedError() + + def clear(self): + raise NotImplementedError() + def status(self): raise NotImplementedError() diff --git a/runbullet/lib/plugins/music/mpd/__init__.py b/runbullet/lib/plugins/music/mpd/__init__.py index 60c5b3fb..9ea37a9c 100644 --- a/runbullet/lib/plugins/music/mpd/__init__.py +++ b/runbullet/lib/plugins/music/mpd/__init__.py @@ -16,6 +16,24 @@ class MusicMpdPlugin(MusicPlugin): def stop(self): self.client.stop() + def next(self): + self.client.next() + + def previous(self): + self.client.previous() + + def setvol(self, vol): + self.client.setvol(vol) + + def add(self, content): + self.client.add(content) + + def playlistadd(self, playlist): + self.client.playlistadd(playlist) + + def clear(self): + self.client.clear() + def status(self): return self.client.status() diff --git a/setup.py b/setup.py index eccf9857..f177245b 100755 --- a/setup.py +++ b/setup.py @@ -1,11 +1,33 @@ #!/usr/bin/env python +import errno import os from setuptools import setup def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() +def pkg_files(dir): + paths = [] + for (path, dirs, files) in os.walk(dir): + for file in files: + paths.append(os.path.join('..', path, file)) + return paths + +def create_etc_dir(): + path = '/etc/runbullet' + try: + os.makedirs(path) + except OSError as e: + if isinstance(e, PermissionError) \ + or e.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise + +plugins = pkg_files('runbullet/lib/plugins') +create_etc_dir() + setup( name = "runbullet", version = "0.1", @@ -15,11 +37,19 @@ setup( license = "BSD", keywords = "pushbullet notifications automation", url = "https://www.fabiomanganiello.com", - packages=['runbullet'], - long_description=read('README.md'), - classifiers=[ + packages = ['runbullet'], + package_data = {'': plugins}, + scripts = ['runbullet/bin/pusher', 'runbullet/bin/runbullet'], + data_files = [ + ('/etc/runbullet', ['runbullet/config.example.yaml']) + ], + long_description = read('README.md'), + classifiers = [ "Topic :: Utilities", "License :: OSI Approved :: BSD License", ], + install_requires = [ + 'pyyaml' + ] )