diff --git a/README.md b/README.md index 084647a..7a8613b 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,15 @@ We have designed two interfaces into jarjar: a shell command and a Python Module # The Shell Command -The [`sh/`](sh/) directory contains a shell command `jarjar` and a configuration file `.jarjar`. +The `sh/` directory contains a shell command `jarjar` and a configuration file `.jarjar`. -Fill the configuration file out with useful defaults. Critically, you'll want to paste in your Slack webhook so that jarjar knows where to send the message. Then put the configuration file in your home directory (`~/`), that's where jarjar will look for it. Don't worry, you can override those defaults later. +Fill the configuration file out with useful defaults. Critically, you'll want to paste in your Slack webhook so that jarjar knows where to send the message. Then put the configuration file in your home directory (`~/`), that's where jarjar will look for it. Don't worry, you can override those defaults later. Here's a sample `.jarjar`: + +```sh +channel="@my_username" # or "#general" +message="Hi! Meesa Jar Jar Binks." +webhook="your-webhook-here" +``` Then, add the `jarjar` _script_ [to your path](https://stackoverflow.com/questions/20054538/add-a-bash-script-to-path). After that, you can use it like so: @@ -50,26 +56,39 @@ jarjar -e -u @username -m "Hi!" -w "their-webhook-url" # The Python Module -This module implements jarjar's functionality more fluidly within Python scripts. Importing the jarjar module provides a simple class, which is initialized by a default webhook and channel (which can be overridden), and sends messages like the shell command. +This module implements jarjar's functionality more fluidly within Python scripts. Importing the jarjar module provides a simple class, which can be used to send messages like the shell command. Installation is simple: -1. Make sure the [`python/jarjar`](python/) folder is on your current path (e.g., copy it to your working directory, or your modules directory). -2. Make sure you've installed [`python-requests`](http://docs.python-requests.org/en/master/). +1. `pip install jarjar` +2. _Optional_: create a `~/.jarjar` file with some defaults (this is shared with the shell command). + +```sh +channel="@my_username" # or "#general" +message="Hi! Meesa Jar Jar Binks." +webhook="your-webhook-here" +``` + Then, you're good to go! You can use it as follows: ```python from jarjar import jarjar -# initialize with defaults -jj = jarjar(channel = '#channel', url = 'slack-webhook-url') +# initialize with defaults from .jarjar +jj = jarjar() + +# initialize with custom defaults +jj = jarjar(channel='#channel', webhook='slack-webhook-url') + +# initialization is not picky -- provide one or both arguments +jj = jarjar(webhook = 'slack-webhook-url') # send a text message jj.text('Hi!') # send a message to multiple channels or users -jj.text('Hi!',channel=["@jeffzemla","#channel"]) +jj.text('Hi!', channel=["@jeffzemla","#channel"]) # send an attachment jj.attach(dict(status='it\'s all good')) @@ -77,35 +96,28 @@ jj.attach(dict(status='it\'s all good')) # send both jj.post(text='Hi', attach=dict(status='it\'s all good')) -# override defaults +# override defaults after initializing jj.attach(dict(status='it\'s all good'), channel = '@jeffzemla') -jj.text('Hi!', channel = '@nolan', url = 'another-webhook') - -# initialization is not picky -jj = jarjar() -jj.text('Hi', channel = '#channel', url = 'slack-webhook-url') - -jj = jarjar(url = 'slack-webhook-url') -jj.attach(dict(status='it\'s all good'), channel = '#channel') +jj.text('Hi!', channel = '@nolan', webhook = 'another-webhook') ``` ## Methods -### text +### `text` > `jj.text(text, **kwargs)` -Send a text message, specified by a string, `text`. User may optionally supply the channel and webhook url in the `kwargs`. +Send a text message, specified by a string, `text`. User may optionally supply the channel and webhook in the `kwargs`. -### attach +### `attach` > `jj.attach(attach, **kwargs)` -Send attachments, specified by values in a dict, `attach`. User may optionally supply the channel and webhook url in the `kwargs`. +Send attachments, specified by values in a dict, `attach`. User may optionally supply the channel and webhook in the `kwargs`. -### post +### `post` -> `jj.post(text=None, attach=None, channel=None, url=None)` +> `jj.post(text=None, attach=None, channel=None, webhook=None)` The generic post method. `jj.text(...)` and `jj.attach(...)` are simply convenience functions wrapped around this method. User may supply text and/or attachments, and may override the default channel and webhook url. diff --git a/python/build/lib/jarjar/__init__.py b/python/build/lib/jarjar/__init__.py new file mode 100644 index 0000000..4e1457a --- /dev/null +++ b/python/build/lib/jarjar/__init__.py @@ -0,0 +1,7 @@ +from sys import version_info as _version_info + +# different importing for python 2 and 3 +if _version_info.major == 2: + from jarjar import jarjar +else: + from jarjar.jarjar import jarjar \ No newline at end of file diff --git a/python/build/lib/jarjar/jarjar.py b/python/build/lib/jarjar/jarjar.py new file mode 100644 index 0000000..ea8ea76 --- /dev/null +++ b/python/build/lib/jarjar/jarjar.py @@ -0,0 +1,160 @@ +import requests +import json +import time +import os +import imp + +class jarjar(): + + def __init__(self, channel=None, webhook=None): + + # read config file, set defaults + self._read_config() + self._set_defaults(channel=channel, webhook=webhook) + + # headers for post request + self.headers = {'Content-Type': 'application/json'} + + + def _set_defaults(self, channel=None, webhook=None): + """ + Set the default channel and webhook + This could be a little drier.... + """ + + # set default channel + if channel is None: + self.default_channel = self.cfg_channel + else: + self.default_channel = channel + + # same thing for webhook + if webhook is None: + self.default_webhook = self.cfg_webhook + else: + self.default_webhook = webhook + + + def _read_config(self): + """ + Read the .jarjar file for defaults. + """ + + # get .jarjar path + filename = os.path.join(os.path.expanduser('~'), '.jarjar') + + # make empty .jarjar if needed + if not os.path.exists(filename): + open(filename, 'a').close() + + # load config + cfg = imp.load_source('_jarjar', filename) + + # assign variables + for field in ['channel','webhook']: + + # read from config, or set to none + if hasattr(cfg, field): + data = getattr(cfg, field) + else: + data = None + + # set value + setattr(self, 'cfg_%s' % field, data) + + + def _args_handler(self, channel, webhook): + """ + Decide to use the default or provided arguments + """ + + # make sure channel and URL are _somewhere_ + if [self.default_channel, channel] == [None, None]: + raise Exception('No channel provided!') + + if [self.default_webhook, webhook] == [None, None]: + raise Exception('No webhook url provided!') + + # use defaults if not overridden + if channel is None: channel = self.default_channel + if webhook is None: webhook = self.default_webhook + + return channel, webhook + + @staticmethod + def _attachment_formatter(attach): + """ + Convert a dict, fields, into a a correctly-formatted + attachment object for Slack. + """ + attachments = dict( + fallback = "New attachments are ready!", + color = "#36a64f", + ts = time.time(), + fields = [] + ) + + field_array = [] + for key in attach: + if isinstance(attach[key], str): outval = attach[key] + else: outval = str(attach[key]) + attachments['fields'].append(dict( + title = key, + value = outval, + short = len(outval) < 20 + )) + + return [attachments] + + def attach(self, attach, **kwargs): + """ + Send an attachment, without text. This is a wrapper around + self.post + """ + return self.post(attach = attach, **kwargs) + + def text(self, text, **kwargs): + """ + Send a message, without attachments. This is a wrapper around + self.post + """ + return self.post(text = text, **kwargs) + + def post(self, text=None, attach=None, channel=None, webhook=None): + """ + Generic method to send a message to slack. Defaults may be overridden. + The user may specify text or attachments. + """ + + # return if there is nothing to send + if [text, attach] == [None, None]: return None + + # get channel and webhook + channel, webhook = self._args_handler(channel, webhook) + + # recursively post to all channels in array of channels + if isinstance(channel, list): + status=[] + for c in channel: + status.append(self.post(text=text, attach=attach, channel=c, url=webhook)) + return status + + # construct a payload + payload = dict(channel = channel) + + # add text and attachments if provided + if text is not None: + payload['text'] = text + + if attach is not None: + payload['attachments']= self._attachment_formatter(attach) + + # convert payload to json and return + payload = json.dumps(payload) + return requests.post(webhook, data=payload, headers=self.headers) + + def set_webhook(self, webhook): + self.default_webhook = webhook + + def set_channel(self, channel): + self.default_channel = channel diff --git a/python/dist/jarjar-2.0-py2.py3-none-any.whl b/python/dist/jarjar-2.0-py2.py3-none-any.whl new file mode 100644 index 0000000..7b32d3b Binary files /dev/null and b/python/dist/jarjar-2.0-py2.py3-none-any.whl differ diff --git a/python/dist/jarjar-2.0.tar.gz b/python/dist/jarjar-2.0.tar.gz new file mode 100644 index 0000000..a9dac17 Binary files /dev/null and b/python/dist/jarjar-2.0.tar.gz differ diff --git a/python/jarjar.egg-info/PKG-INFO b/python/jarjar.egg-info/PKG-INFO new file mode 100644 index 0000000..2510582 --- /dev/null +++ b/python/jarjar.egg-info/PKG-INFO @@ -0,0 +1,17 @@ +Metadata-Version: 1.1 +Name: jarjar +Version: 2.0 +Summary: Programatically send messages to your slack team +Home-page: https://github.com/AusterweilLab/jarjar +Author: The Austerweil Lab at UW-Madison +Author-email: austerweil.lab@gmail.com +License: MIT +Description: UNKNOWN +Keywords: slack,messaging +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 diff --git a/python/jarjar.egg-info/SOURCES.txt b/python/jarjar.egg-info/SOURCES.txt new file mode 100644 index 0000000..d56c343 --- /dev/null +++ b/python/jarjar.egg-info/SOURCES.txt @@ -0,0 +1,9 @@ +setup.cfg +setup.py +jarjar/__init__.py +jarjar/jarjar.py +jarjar.egg-info/PKG-INFO +jarjar.egg-info/SOURCES.txt +jarjar.egg-info/dependency_links.txt +jarjar.egg-info/requires.txt +jarjar.egg-info/top_level.txt \ No newline at end of file diff --git a/python/jarjar.egg-info/dependency_links.txt b/python/jarjar.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/python/jarjar.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/python/jarjar.egg-info/requires.txt b/python/jarjar.egg-info/requires.txt new file mode 100644 index 0000000..cf56d5b --- /dev/null +++ b/python/jarjar.egg-info/requires.txt @@ -0,0 +1 @@ +requests>=2 diff --git a/python/jarjar.egg-info/top_level.txt b/python/jarjar.egg-info/top_level.txt new file mode 100644 index 0000000..8ed2eec --- /dev/null +++ b/python/jarjar.egg-info/top_level.txt @@ -0,0 +1 @@ +jarjar diff --git a/python/jarjar/__init__.py b/python/jarjar/__init__.py index 5a80aed..4e1457a 100644 --- a/python/jarjar/__init__.py +++ b/python/jarjar/__init__.py @@ -1 +1,7 @@ -from jarjar import jarjar +from sys import version_info as _version_info + +# different importing for python 2 and 3 +if _version_info.major == 2: + from jarjar import jarjar +else: + from jarjar.jarjar import jarjar \ No newline at end of file diff --git a/python/jarjar/jarjar.py b/python/jarjar/jarjar.py index 923383f..ea8ea76 100644 --- a/python/jarjar/jarjar.py +++ b/python/jarjar/jarjar.py @@ -1,14 +1,69 @@ import requests import json import time +import os +import imp class jarjar(): - def __init__(self, channel=None, url=None): - self.default_channel = channel - self.default_url = url - self.headers ={'Content-Type': 'application/json'} - def _args_handler(self, channel, url): + def __init__(self, channel=None, webhook=None): + + # read config file, set defaults + self._read_config() + self._set_defaults(channel=channel, webhook=webhook) + + # headers for post request + self.headers = {'Content-Type': 'application/json'} + + + def _set_defaults(self, channel=None, webhook=None): + """ + Set the default channel and webhook + This could be a little drier.... + """ + + # set default channel + if channel is None: + self.default_channel = self.cfg_channel + else: + self.default_channel = channel + + # same thing for webhook + if webhook is None: + self.default_webhook = self.cfg_webhook + else: + self.default_webhook = webhook + + + def _read_config(self): + """ + Read the .jarjar file for defaults. + """ + + # get .jarjar path + filename = os.path.join(os.path.expanduser('~'), '.jarjar') + + # make empty .jarjar if needed + if not os.path.exists(filename): + open(filename, 'a').close() + + # load config + cfg = imp.load_source('_jarjar', filename) + + # assign variables + for field in ['channel','webhook']: + + # read from config, or set to none + if hasattr(cfg, field): + data = getattr(cfg, field) + else: + data = None + + # set value + setattr(self, 'cfg_%s' % field, data) + + + def _args_handler(self, channel, webhook): """ Decide to use the default or provided arguments """ @@ -16,14 +71,15 @@ def _args_handler(self, channel, url): # make sure channel and URL are _somewhere_ if [self.default_channel, channel] == [None, None]: raise Exception('No channel provided!') - if [self.default_url, url] == [None, None]: + + if [self.default_webhook, webhook] == [None, None]: raise Exception('No webhook url provided!') # use defaults if not overridden if channel is None: channel = self.default_channel - if url is None: url = self.default_url + if webhook is None: webhook = self.default_webhook - return channel, url + return channel, webhook @staticmethod def _attachment_formatter(attach): @@ -64,7 +120,7 @@ def text(self, text, **kwargs): """ return self.post(text = text, **kwargs) - def post(self, text=None, attach=None, channel=None, url=None): + def post(self, text=None, attach=None, channel=None, webhook=None): """ Generic method to send a message to slack. Defaults may be overridden. The user may specify text or attachments. @@ -73,14 +129,14 @@ def post(self, text=None, attach=None, channel=None, url=None): # return if there is nothing to send if [text, attach] == [None, None]: return None - # get channel and URL - channel, url = self._args_handler(channel, url) + # get channel and webhook + channel, webhook = self._args_handler(channel, webhook) # recursively post to all channels in array of channels if isinstance(channel, list): status=[] for c in channel: - status.append(self.post(text=text, attach=attach, channel=c, url=url)) + status.append(self.post(text=text, attach=attach, channel=c, url=webhook)) return status # construct a payload @@ -95,10 +151,10 @@ def post(self, text=None, attach=None, channel=None, url=None): # convert payload to json and return payload = json.dumps(payload) - return requests.post(url, data=payload, headers=self.headers) + return requests.post(webhook, data=payload, headers=self.headers) - def set_url(self, url): - self.default_url = url + def set_webhook(self, webhook): + self.default_webhook = webhook def set_channel(self, channel): self.default_channel = channel diff --git a/python/license.txt b/python/license.txt new file mode 100644 index 0000000..57a14b0 --- /dev/null +++ b/python/license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 The Austerweil Lab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/python/setup.cfg b/python/setup.cfg new file mode 100644 index 0000000..3480374 --- /dev/null +++ b/python/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 \ No newline at end of file diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..b2d7c89 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,21 @@ +from setuptools import setup + +setup(name='jarjar', + version='2.0', + description='Programatically send messages to your slack team', + url='https://github.com/AusterweilLab/jarjar', + author='The Austerweil Lab at UW-Madison', + author_email='austerweil.lab@gmail.com', + license='MIT', + keywords=['slack', 'messaging'], + packages=['jarjar'], + install_requires = ['requests>=2'], + classifiers=[ + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ] + ) \ No newline at end of file