Skip to content

Commit

Permalink
General python3 support
Browse files Browse the repository at this point in the history
- Better py3 support removing imp when possible
- Default message personalization
- Possibility of creating a custom docs reply
- PluginManagerinto it's own file as is used by dispatcher too
  • Loading branch information
merqurio authored and josegonzalez committed Feb 19, 2016
1 parent 584b499 commit 3a9a055
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 66 deletions.
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ First you need to get the slack api token for your bot. You have two options:
1. If you use a [bot user integration](https://api.slack.com/bot-users) of slack, you can get the api token on the integration page.
2. If you use a real slack user, you can generate an api token on [slack web api page](https://api.slack.com/web).

### Configure the api token

Then you need to configure the `API_TOKEN` in a python module `slackbot_settings.py`, which must be located in a python import path.
### Configure the bot
First create a `slackbot_settings.py` and a `run.py` in your own instance of slackbot.

##### Configure the api token

Then you need to configure the `API_TOKEN` in a python module `slackbot_settings.py`, which must be located in a python import path. This will be automatically imported by the bot.

slackbot_settings.py:

Expand All @@ -39,7 +43,7 @@ API_TOKEN = "<your-api-token>"

Alternatively, you can use the environment variable `SLACKBOT_API_TOKEN`.

### Run the bot
##### Run the bot

```python
from slackbot.bot import Bot
Expand All @@ -50,6 +54,24 @@ def main():
if __name__ == "__main__":
main()
```
##### Configure the default answer
Add to `slackbot_settings.py` a default_reply:
```python
default_reply = "Sorry but I didn't understood you"
```

##### Configure the docs answer
The `message` attribute passed to [your custom plugins](#create-plugins) has an special function `message.docs_reply()` that will parse all the plugins available and return the Docs in each of them.

##### Configure the plugins
Add [your plugin modules](#create-plugins) to a `PLUGINS` list in `slackbot_settings.py`:

```python
PLUGINS = [
'slackbot.plugins',
'mybot.plugins',
]
```

Now you can talk to your bot in your slack client!

Expand All @@ -73,8 +95,7 @@ def github():
}]
message.send_webapi('', json.dumps(attachments))
```

## Plugins
## Create Plugins

A chat bot is meaningless unless you can extend/customize it to fit your own use cases.

Expand Down
1 change: 1 addition & 0 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from slackbot import settings
from slackbot.bot import Bot


def main():
kw = {
'format': '[%(asctime)s] %(message)s',
Expand Down
52 changes: 3 additions & 49 deletions slackbot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
import imp
import importlib
import logging
import os
import re
import time
from glob import glob
from six.moves import _thread

from slackbot import settings
from slackbot.manager import PluginsManager
from slackbot.slackclient import SlackClient
from slackbot.utils import to_utf8
from slackbot.dispatcher import MessageDispatcher

logger = logging.getLogger(__name__)
Expand All @@ -21,8 +19,8 @@ class Bot(object):
def __init__(self):
self._client = SlackClient(
settings.API_TOKEN,
bot_icon = settings.BOT_ICON if hasattr(settings, 'BOT_ICON') else None,
bot_emoji = settings.BOT_EMOJI if hasattr(settings, 'BOT_EMOJI') else None
bot_icon=settings.BOT_ICON if hasattr(settings, 'BOT_ICON') else None,
bot_emoji=settings.BOT_EMOJI if hasattr(settings, 'BOT_EMOJI') else None
)
self._plugins = PluginsManager()
self._dispatcher = MessageDispatcher(self._client, self._plugins)
Expand All @@ -42,50 +40,6 @@ def _keepactive(self):
self._client.ping()


class PluginsManager(object):
commands = {
'respond_to': {},
'listen_to': {}
}

def __init__(self):
pass

def init_plugins(self):
if hasattr(settings, 'PLUGINS'):
plugins = settings.PLUGINS
else:
plugins = 'slackbot.plugins'

for plugin in plugins:
self._load_plugins(plugin)

def _load_plugins(self, plugin):
logger.info('loading plugin "%s"', plugin)
path_name = None
for mod in plugin.split('.'):
if path_name is not None:
path_name = [path_name]
_, path_name, _ = imp.find_module(mod, path_name)
for pyfile in glob('{}/[!_]*.py'.format(path_name)):
module = '.'.join((plugin, os.path.split(pyfile)[-1][:-3]))
try:
importlib.import_module(module)
except:
logger.exception('Failed to import %s', module)

def get_plugins(self, category, text):
has_matching_plugin = False
for matcher in self.commands[category]:
m = matcher.search(text)
if m:
has_matching_plugin = True
yield self.commands[category][matcher], to_utf8(m.groups())

if not has_matching_plugin:
yield None, None


def respond_to(matchstr, flags=0):
def wrapper(func):
PluginsManager.commands['respond_to'][re.compile(matchstr, flags)] = func
Expand Down
31 changes: 22 additions & 9 deletions slackbot/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import time
import traceback
from six import iteritems
from slackbot.manager import PluginsManager
from slackbot.utils import to_utf8, WorkerPool

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -96,20 +97,28 @@ def loop(self):
time.sleep(1)

def _default_reply(self, msg):
default_reply = [
u'Bad command "%s", You can ask me one of the following questions:\n' % msg['text'],
]
default_reply += [u' • `{0}` {1}'.format(p.pattern, v.__doc__ or "")
for p, v in iteritems(self._plugins.commands['respond_to'])]

self._client.rtm_send_message(msg['channel'],
'\n'.join(to_utf8(default_reply)))
try:
from slackbot_settings import default_reply
default_reply = to_utf8(default_reply)

except ImportError:

default_reply = [
'Bad command "%s", You can ask me one of the following questions:\n' % msg['text'],
]
default_reply += [' • `{0}` {1}'.format(p.pattern, v.__doc__ or "")
for p, v in iteritems(self._plugins.commands['respond_to'])]

default_reply = '\n'.join(to_utf8(default_reply))

self._client.rtm_send_message(msg['channel'], default_reply)


class Message(object):
def __init__(self, slackclient, body):
self._client = slackclient
self._body = body
self._plugins = PluginsManager()

def _get_user_id(self):
if 'user' in self._body:
Expand All @@ -118,7 +127,7 @@ def _get_user_id(self):
return self._client.find_user_by_name(self._body['username'])

def _gen_at_message(self, text):
text = u'<@{}>: {}'.format(self._get_user_id(), text)
text = '<@{}>: {}'.format(self._get_user_id(), text)
return text

def _gen_reply(self, text):
Expand Down Expand Up @@ -186,3 +195,7 @@ def channel(self):
@property
def body(self):
return self._body

def docs_reply(self):
reply = [' • `{0}` {1}'.format(v.__name__, v.__doc__ or "") for p, v in iteritems(self._plugins.commands['respond_to'])]
return '\n'.join(to_utf8(reply))
65 changes: 65 additions & 0 deletions slackbot/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-

import os
import logging
from glob import glob
from six import PY2
from importlib import import_module
from slackbot import settings
from slackbot.utils import to_utf8

logger = logging.getLogger(__name__)


class PluginsManager(object):
def __init__(self):
pass

commands = {
'respond_to': {},
'listen_to': {}
}

def init_plugins(self):
if hasattr(settings, 'PLUGINS'):
plugins = settings.PLUGINS
else:
plugins = 'slackbot.plugins'

for plugin in plugins:
self._load_plugins(plugin)

def _load_plugins(self, plugin):
logger.info('loading plugin "%s"', plugin)
path_name = None

if PY2:
import imp

for mod in plugin.split('.'):
if path_name is not None:
path_name = [path_name]
_, path_name, _ = imp.find_module(mod, path_name)
else:
from importlib.util import find_spec as importlib_find

path_name = importlib_find(plugin).submodule_search_locations[0]

for pyfile in glob('{}/[!_]*.py'.format(path_name)):
module = '.'.join((plugin, os.path.split(pyfile)[-1][:-3]))
try:
import_module(module)
except:
# TODO Better exception handling
logger.exception('Failed to import %s', module)

def get_plugins(self, category, text):
has_matching_plugin = False
for matcher in self.commands[category]:
m = matcher.search(text)
if m:
has_matching_plugin = True
yield self.commands[category][matcher], to_utf8(m.groups())

if not has_matching_plugin:
yield None, None
1 change: 1 addition & 0 deletions slackbot/plugins/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from slackbot.bot import respond_to
from slackbot.utils import download_file, create_tmp_file


@respond_to(r'upload \<?(.*)\>?')
def upload(message, url):
url = url.lstrip('<').rstrip('>')
Expand Down
2 changes: 2 additions & 0 deletions slackbot/settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-

import os

DEBUG = False
Expand Down
2 changes: 1 addition & 1 deletion slackbot/slackclient.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#coding: UTF-8
# -*- coding: utf-8 -*-

from __future__ import print_function, absolute_import
import os
Expand Down
3 changes: 1 addition & 2 deletions slackbot/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#coding: UTF-8

# -*- coding: utf-8 -*-

import os
import logging
Expand Down

0 comments on commit 3a9a055

Please sign in to comment.