SlackBorg is a framework for semi-conversational, multi-step workflow Slack Bots. We're just getting started. Have an idea? File an issue. Have some code? Open a pull request.
SlackBorg has two main things you should care about, the @command
decorator and the Conversation
class.
@command
is a decorator you place on a function you implement to
respond to a given command. A command can be multi_step
, which is to
say that its function is called on the given conversation each time a
new message comes in, until the conversation is explicitly closed. It's
up to you how you handle a multi-step conversation. As you'll see, the
Conversation
object includes all the data you need to make decisions
each time your handler is called. If your command is not multi_step
,
the conversation is closed immediately after your handler returns.
match_string
: A RegEx string that you would pass intore.compile
. Requiredflags
: Any flags from there
module you would pass intore.compile
.multi_step
: Set toTrue
if your command is multi-step. DefaultFalse
.
The Conversation
object is the sole parameter to your command
handler. Conversations are unique to a given user in a given channel.
When a message comes in, the conversation manager checks to see if a
conversation exists for the sending user in the channel it is posted to.
If it doesn't yet exist, a Conversation object is constructed and the
command manager attempts to match a command to the Conversation. If it
finds one, its handler is called on the conversation. If a conversation
exists, it is updated with the latest message and its command's handler
called on the updated Conversation.
user_id
: the sender's Slack User IDuser_data
: the sender's full Slack User Data (fetched when the Conversation is first created)channel_id
: the Conversation's Slack Channel IDchannel_data
: the Conversation's full Slack Channel Data (fetched when the Conversation is first created -- only set if it's a channel and not a DM)initial_message
: the message that was responsible for the creation of this Conversation.messages
: the rest of the messages. Does not include ``initial_message``.latest_message
: the most recent message.context
: a dictionary that you can put any data you want to persist in the conversation across messages. This is where the magic is for doing a multi-step command across a conversation.
say(message)
: send a message to the channel.close()
: close the conversation.
Right now, I'd probably suggest spinning up a virtualenv
and running
python setup.py develop
inside of it.
Write yourself a script to declare your commands and run your bot, like so:
import os import random import re import time from slackborg import * # Get Bot ID and API Token from env -- make sure to put these in your env! BOT_ID = os.environ.get('SLACK_BOT_ID') BOT_TOKEN = os.environ.get('SLACK_BOT_TOKEN') # Define a default response to any non-matching commands. The default default is to silently ignore the command and close the conversation. @default_command def default_cmd(conversation): conversation.say("I see, sir {}".format(conversation.user_data['profile']['first_name'])) # Define a command by a regex string, and optionally any flags you'd give the re.compile method. @command('hello', flags=re.IGNORECASE) def hello(conversation): conversation.say("Hello! I am C-3PO, human-cyborg relations!") # By default, a command is single-step and auto-closes its conversation upon the handler returning. # You can override this. @command('sum', flags=re.IGNORECASE, multi_step=True) def do_sum(c): print c operands = c.context.setdefault('operands', []) if len(c.messages): if 'done' in c.latest_message.lower(): c.say("The sum of {} = {}".format( " + ".join(str(o) for o in operands), str(sum(operands)) ) ) c.close() else: try: operands.append(int(c.latest_message)) c.say("So far: {}...".format( " + ".join(str(o) for o in operands) )) except: c.say("That input wasn't a number. Try again!") else: c.say("Just enter your operands, one by one, and then type `done` when you're done!") def main(): borg = SlackBorg(BOT_ID, BOT_TOKEN) borg.run() if __name__ == '__main__': main() # End
Some of the patterns used in this framework borrow ideologies from _lins05/slackbot, so I thank the existing developers of that library for their prior work. Perhaps we can bring these two libraries together, eventually! Or keep them separate :D