-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Complete rewrite of the bot to support multiple team deployment on Be…
…ep Boop.
- Loading branch information
Showing
13 changed files
with
259 additions
and
303 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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({}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.