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. 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' lib_dir = curdir + os.sep + 'lib'
sys.path.insert(0, lib_dir) sys.path.insert(0, lib_dir)
DEVICE_ID = None
modules = {} modules = {}
plugins = {} plugins = {}
config = {}
def on_open(ws): def on_open(ws):
@ -56,8 +56,10 @@ def _init_plugin(plugin, reload=False):
) + 'Plugin' ) + 'Plugin'
if cls_name not in plugins or reload: if cls_name not in plugins or reload:
plugin_conf = config[plugin] if plugin in config else {}
try: 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: except AttributeError as e:
logging.warn('No such class in {}: {}'.format( logging.warn('No such class in {}: {}'.format(
module_name, cls_name)) module_name, cls_name))
@ -69,7 +71,7 @@ def _init_plugin(plugin, reload=False):
def _exec_func(body, retry=True): def _exec_func(body, retry=True):
try: try:
logging.info('Received push addressed to me: {}'.format(body)) 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: if 'plugin' not in body:
logging.warn('No plugin specified') logging.warn('No plugin specified')
return return
@ -105,8 +107,6 @@ def _exec_func(body, retry=True):
def _on_push(ws, data): def _on_push(ws, data):
global DEVICE_ID
data = json.loads(data) data = json.loads(data)
if data['type'] == 'tickle' and data['subtype'] == 'push': if data['type'] == 'tickle' and data['subtype'] == 'push':
logging.debug('Received push tickle') logging.debug('Received push tickle')
@ -127,7 +127,7 @@ def _on_push(ws, data):
except ValueError as e: except ValueError as e:
return 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 return # Not for me
if 'plugin' not in body: if 'plugin' not in body:
@ -143,11 +143,35 @@ def on_push(ws, data):
on_error(ws, e) on_error(ws, e)
def main(): def parse_config_file(config_file=None):
global DEVICE_ID 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 DEBUG = False
config_file = curdir + os.sep + 'config.yaml' config_file = None
optlist, args = getopt(sys.argv[1:], 'vh') optlist, args = getopt(sys.argv[1:], 'vh')
for opt, arg in optlist: for opt, arg in optlist:
@ -164,13 +188,10 @@ Usage: {} [-v] [-h] [-c <config_file>]
'''.format(sys.argv[0])) '''.format(sys.argv[0]))
return return
config = {} config = parse_config_file(config_file)
with open(config_file,'r') as f:
config = yaml.load(f)
API_KEY = config['pushbullet']['token'] if 'device_id' not in config:
DEVICE_ID = config['device_id'] \ config['device_id'] = socket.gethostname()
if 'device_id' in config else socket.gethostname()
if 'debug' in config: if 'debug' in config:
DEBUG = config['debug'] DEBUG = config['debug']
@ -182,7 +203,7 @@ Usage: {} [-v] [-h] [-c <config_file>]
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
ws = websocket.WebSocketApp('wss://stream.pushbullet.com/websocket/' + ws = websocket.WebSocketApp('wss://stream.pushbullet.com/websocket/' +
API_KEY, config['pushbullet']['token'],
on_message = on_push, on_message = on_push,
on_error = on_error, on_error = on_error,
on_close = on_close) 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 json
import os import os
import sys import sys
@ -5,16 +7,8 @@ import yaml
from getopt import getopt from getopt import getopt
from pushbullet import Pushbullet 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(): def print_usage():
print ('''Usage: {} [-h|--help] <-t|--target <target name>> <-p|--plugin <plugin name>> payload print ('''Usage: {} [-h|--help] <-t|--target <target name>> <-p|--plugin <plugin name>> payload
@ -25,7 +19,7 @@ def print_usage():
'''.format(sys.argv[0])) '''.format(sys.argv[0]))
def main(): def main():
config = get_config() config = parse_config_file()
API_KEY = config['pushbullet']['token'] API_KEY = config['pushbullet']['token']
pb = Pushbullet(API_KEY) 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 os
import sys import sys
import yaml
class Plugin(object): class Plugin(object):
def __init__(self): def __init__(self, config):
self.config = config
for cls in reversed(self.__class__.mro()): for cls in reversed(self.__class__.mro()):
if cls is not object: if cls is not object:
try: try:
@ -12,19 +13,6 @@ class Plugin(object):
pass 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): def run(self, args):
raise NotImplementedError() raise NotImplementedError()

View file

@ -2,11 +2,29 @@ from .. import Plugin
class MusicPlugin(Plugin): class MusicPlugin(Plugin):
def run(self, args): 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() self.play()
elif 'pause' in args and self.status()['state'] != 'pause': elif 'pause' in args and args['pause'] and status['state'] != 'pause':
self.pause() self.pause()
elif 'stop' in args: elif 'stop' in args and args['stop']:
self.stop() self.stop()
return self.status() return self.status()
@ -20,6 +38,24 @@ class MusicPlugin(Plugin):
def stop(self): def stop(self):
raise NotImplementedError() 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): def status(self):
raise NotImplementedError() raise NotImplementedError()

View file

@ -16,6 +16,24 @@ class MusicMpdPlugin(MusicPlugin):
def stop(self): def stop(self):
self.client.stop() 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): def status(self):
return self.client.status() return self.client.status()

View file

@ -1,11 +1,33 @@
#!/usr/bin/env python #!/usr/bin/env python
import errno
import os import os
from setuptools import setup from setuptools import setup
def read(fname): def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read() 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( setup(
name = "runbullet", name = "runbullet",
version = "0.1", version = "0.1",
@ -16,10 +38,18 @@ setup(
keywords = "pushbullet notifications automation", keywords = "pushbullet notifications automation",
url = "https://www.fabiomanganiello.com", url = "https://www.fabiomanganiello.com",
packages = ['runbullet'], 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'), long_description = read('README.md'),
classifiers = [ classifiers = [
"Topic :: Utilities", "Topic :: Utilities",
"License :: OSI Approved :: BSD License", "License :: OSI Approved :: BSD License",
], ],
install_requires = [
'pyyaml'
]
) )