Proper release with setuptools support
This commit is contained in:
parent
173ce6782f
commit
99a93012ce
8 changed files with 159 additions and 47 deletions
17
README.md
17
README.md
|
@ -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)).
|
||||||
|
|
||||||
|
|
|
@ -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
14
runbullet/pusher.py → runbullet/bin/pusher
Normal file → Executable 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
8
runbullet/bin/runbullet
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!python
|
||||||
|
|
||||||
|
import runbullet
|
||||||
|
|
||||||
|
runbullet.main()
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
30
setup.py
30
setup.py
|
@ -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'
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue