Skip to content

Commit

Permalink
Complete rewrite of the bot to support multiple team deployment on Be…
Browse files Browse the repository at this point in the history
…ep Boop.
  • Loading branch information
randompi committed Mar 28, 2016
1 parent 6974484 commit 8fbf77b
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 303 deletions.
File renamed without changes.
24 changes: 24 additions & 0 deletions bot/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python

import logging
import os

from slack_bot import SlackBot

logger = logging.getLogger(__name__)


if __name__ == "__main__":

log_level = os.getenv("LOG_LEVEL", "INFO")
logging.basicConfig(format='%(asctime)s - %(levelname)s: %(message)s', level=log_level)

slack_token = os.getenv("SLACK_TOKEN", "")
logging.info("token: {}".format(slack_token))

if slack_token == "":
logging.info("SLACK_TOKEN env var not set, expecting token to be provided by Resourcer events")
slack_token = None

bot = SlackBot(slack_token)
bot.start({})
52 changes: 52 additions & 0 deletions bot/event_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import json
import logging
import re

logger = logging.getLogger(__name__)


class RtmEventHandler(object):
def __init__(self, slack_clients, msg_writer):
self.clients = slack_clients
self.msg_writer = msg_writer

def handle(self, event):

if 'type' in event:
self._handle_by_type(event['type'], event)

def _handle_by_type(self, event_type, event):
# See https://api.slack.com/rtm for a full list of events
if event_type == 'error':
# error
self.msg_writer.write_error(event['channel'], json.dumps(event))
elif event_type == 'message':
# message was sent to channel
self._handle_message(event)
elif event_type == 'channel_joined':
# you joined a channel
self.msg_writer.write_help_message(event['channel'])
elif event_type == 'group_joined':
# you joined a private group
self.msg_writer.write_help_message(event['channel'])
else:
pass

def _handle_message(self, event):
# Filter out messages from the bot itself
if not self.clients.is_message_from_me(event['user']):

msg_txt = event['text']

if self.clients.is_bot_mention(msg_txt):
# e.g. user typed: "@pybot tell me a joke!"
if 'help' in msg_txt:
self.msg_writer.write_help_message(event['channel'])
elif re.search('hi|hey|hello|howdy', msg_txt):
self.msg_writer.write_greeting(event['channel'], event['user'])
elif 'joke' in msg_txt:
self.msg_writer.write_joke(event['channel'])
elif 'attachment' in msg_txt:
self.msg_writer.demo_attachment(event['channel'])
else:
self.msg_writer.write_prompt(event['channel'])
Binary file added bot/event_handler.pyc
Binary file not shown.
61 changes: 61 additions & 0 deletions bot/messenger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import logging
import random

logger = logging.getLogger(__name__)


class Messenger(object):
def __init__(self, slack_clients):
self.clients = slack_clients

def send_message(self, channel_id, msg):
# in the case of Group and Private channels, RTM channel payload is a complex dictionary
if isinstance(channel_id, dict):
channel_id = channel_id['id']
logger.debug('Sending msg: {} to channel: {}'.format(msg, channel_id))
channel = self.clients.rtm.server.channels.find(channel_id)
channel.send_message("{}".format(msg.encode('ascii', 'ignore')))

def write_help_message(self, channel_id):
bot_uid = self.clients.bot_user_id()
txt = '{}\n{}\n{}\n{}'.format(
"I'm your friendly Slack bot written in Python. I'll *_respond_* to the following commands:",
"> `hi <@" + bot_uid + ">` - I'll respond with a randomized greeting mentioning your user. :wave:",
"> `<@" + bot_uid + "> joke` - I'll tell you one of my finest jokes, with a typing pause for effect. :laughing:",
"> `<@" + bot_uid + "> attachment` - I'll demo a post with an attachment using the Web API. :paperclip:")
self.send_message(channel_id, txt)

def write_greeting(self, channel_id, user_id):
greetings = ['Hi', 'Hello', 'Nice to meet you', 'Howdy', 'Salutations']
txt = '{}, <@{}>!'.format(random.choice(greetings), user_id)
self.send_message(channel_id, txt)

def write_prompt(self, channel_id):
bot_uid = self.clients.bot_user_id()
txt = "I'm sorry, I didn't quite understand... Can I help you? (e.g. `<@" + bot_uid + "> help`)"
self.send_message(channel_id, txt)

def write_joke(self, channel_id):
question = "Why did the python cross the road?"
self.send_message(channel_id, question)
self.clients.send_user_typing_pause(channel_id)
answer = "To eat the chicken on the other side! :laughing:"
self.send_message(channel_id, answer)


def write_error(self, channel_id, err_msg):
txt = ":face_with_head_bandage: my maker didn't handle this error very well:\n>```{}```".format(err_msg)
self.send_message(channel_id, txt)

def demo_attachment(self, channel_id):
txt = "Beep Beep Boop is a ridiculously simple hosting platform for your Slackbots."
attachment = {
"pretext": "We bring bots to life. :sunglasses: :thumbsup:",
"title": "Host, deploy and share your bot in seconds.",
"title_link": "https://beepboophq.com/",
"text": txt,
"fallback": txt,
"image_url": "https://storage.googleapis.com/beepboophq/_assets/bot-1.22f6fb.png",
"color": "#7CD197",
}
self.clients.web.chat.post_message(channel_id, txt, attachments=[attachment], as_user='true')
Binary file added bot/messenger.pyc
Binary file not shown.
75 changes: 75 additions & 0 deletions bot/slack_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import time
import logging
import traceback

from slack_clients import SlackClients
from messenger import Messenger
from event_handler import RtmEventHandler

logger = logging.getLogger(__name__)


class SlackBot(object):
def __init__(self, token=None):
"""Creates Slacker Web and RTM clients with API Bot User token.
Args:
token (str): Slack API Bot User token (for development token set in env)
"""
self.last_ping = 0
self.keep_running = True
if token is not None:
self.clients = SlackClients(token)

def start(self, resource):
"""Creates Slack Web and RTM clients for the given Resource
using the provided API tokens and configuration, then connects websocket
and listens for RTM events.
Args:
resource (dict of Resource JSON): See payload here - http://linktoresourcejson
"""
if 'SlackBotAccessToken' in resource:
res_access_token = resource['SlackBotAccessToken']
self.clients = SlackClients(res_access_token)

if self.clients.rtm.rtm_connect():
logging.info(u'Connected {} to {} team at https://{}.slack.com'.format(
self.clients.rtm.server.username,
self.clients.rtm.server.login_data['team']['name'],
self.clients.rtm.server.domain))

msg_writer = Messenger(self.clients)
event_handler = RtmEventHandler(self.clients, msg_writer)

while self.keep_running:
for event in self.clients.rtm.rtm_read():
try:
event_handler.handle(event)
except:
err_msg = traceback.format_exc()
logging.error('Unexpected error: {}'.format(err_msg))
msg_writer.write_error(event['channel'], err_msg)
continue

self._auto_ping()
time.sleep(.1)

else:
logger.error('Failed to connect to RTM client with token: {}'.format(self.clients.token))

def _auto_ping(self):
# hard code the interval to 3 seconds
now = int(time.time())
if now > self.last_ping + 3:
self.clients.rtm.server.ping()
self.last_ping = now

def stop(self, resource):
"""Stop any polling loops on clients, clean up any resources,
close connections if possible.
Args:
resource (dict of Resource JSON): See payload here - http://linktoresourcejson
"""
self.keep_running = False
Binary file added bot/slack_bot.pyc
Binary file not shown.
38 changes: 38 additions & 0 deletions bot/slack_clients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

import logging
import re
import time

from slacker import Slacker
from slackclient import SlackClient

logger = logging.getLogger(__name__)


class SlackClients(object):
def __init__(self, token):
self.token = token

# Slacker is a Slack Web API Client
self.web = Slacker(token)

# SlackClient is a Slack Websocket RTM API Client
self.rtm = SlackClient(token)

def bot_user_id(self):
return self.rtm.server.login_data['self']['id']

def is_message_from_me(self, user):
return user == self.rtm.server.login_data['self']['id']

def is_bot_mention(self, message):
bot_user_name = self.rtm.server.login_data['self']['id']
if re.search("@{}".format(bot_user_name), message):
return True
else:
return False

def send_user_typing_pause(self, channel_id, sleep_time=3.0):
user_typing_json = {"type": "typing", "channel": channel_id}
self.rtm.server.send_to_websocket(user_typing_json)
time.sleep(sleep_time)
Binary file added bot/slack_clients.pyc
Binary file not shown.
62 changes: 0 additions & 62 deletions plugins/starter.py

This file was deleted.

14 changes: 9 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
requests
python-daemon
pyyaml
websocket-client
slackclient
docutils==0.12
lockfile==0.12.2
python-daemon==2.1.1
PyYAML==3.11
requests==2.9.1
six==1.10.0
slackclient==1.0.0
slacker==0.9.9
websocket-client==0.35.0
Loading

0 comments on commit 8fbf77b

Please sign in to comment.