-
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.
Merge pull request #6 from BeepBoopHQ/multi-team-bot
Bot Rewrite and Multi team bot support
- Loading branch information
Showing
11 changed files
with
314 additions
and
312 deletions.
There are no files selected for viewing
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
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
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,32 @@ | ||
#!/usr/bin/env python | ||
|
||
import logging | ||
import os | ||
|
||
from beepboop import resourcer | ||
from beepboop import bot_manager | ||
|
||
from slack_bot import SlackBot | ||
from slack_bot import spawn_bot | ||
|
||
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 | ||
botManager = bot_manager.BotManager(spawn_bot) | ||
res = resourcer.Resourcer(botManager) | ||
res.start() | ||
else: | ||
# only want to run a single instance of the bot in dev mode | ||
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']) |
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') |
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,80 @@ | ||
import time | ||
import logging | ||
import traceback | ||
|
||
from slack_clients import SlackClients | ||
from messenger import Messenger | ||
from event_handler import RtmEventHandler | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def spawn_bot(): | ||
return SlackBot() | ||
|
||
|
||
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 message payloads - https://beepboophq.com/docs/article/resourcer-api | ||
""" | ||
logger.debug('Starting bot for resource: {}'.format(resource)) | ||
if 'resource' in resource and 'SlackBotAccessToken' in resource['resource']: | ||
res_access_token = resource['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 message payloads - https://beepboophq.com/docs/article/resourcer-api | ||
""" | ||
self.keep_running = False |
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) |
Oops, something went wrong.