Proper release with setuptools support

This commit is contained in:
Fabio Manganiello 2017-11-03 15:06:29 +01:00
parent 173ce6782f
commit 99a93012ce
8 changed files with 159 additions and 47 deletions

View file

@ -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)).

View file

@ -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 <config_file>]
'''.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 <config_file>]
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)

14
runbullet/pusher.py → runbullet/bin/pusher Normal file → Executable file
View file

@ -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 <target name>> <-p|--plugin <plugin name>> 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)

8
runbullet/bin/runbullet Executable file
View file

@ -0,0 +1,8 @@
#!python
import runbullet
runbullet.main()
# vim:sw=4:ts=4:et:

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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",
@ -16,10 +38,18 @@ setup(
keywords = "pushbullet notifications automation",
url = "https://www.fabiomanganiello.com",
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'
]
)