In Part 1 of this series, I introduced you to SlackKit and Zewo, which allows us to build and deploy a Slack bot written in Swift to a Linux server. Here in Part 2, we will finish the app, showing all of the Swift code. We will also show how to get an API token, how to test the app and deploy it on Heroku, and finally how to launch it.
Finally, some Swift code! To create our bot, we need to edit our main.swift file to contain our bot logic:
import String
importSlackKit
class Leaderboard: MessageEventsDelegate {
// A dictionary to hold our leaderboard
var leaderboard: [String: Int] = [String: Int]()
letatSet = CharacterSet(characters: ["@"])
// A SlackKit client instance
let client: SlackClient
// Initalize the leaderboard with a valid Slack API token
init(token: String) {
client = SlackClient(apiToken: token)
client.messageEventsDelegate = self
}
// Enum to hold commands the bot knows
enum Command: String {
case Leaderboard = "leaderboard"
}
// Enum to hold logic that triggers certain bot behaviors
enum Trigger: String {
casePlusPlus = "++"
caseMinusMinus = "--"
}
// MARK: MessageEventsDelegate
// Listen to the messages that are coming in over the Slack RTM connection
funcmessageReceived(message: Message) {
listen(message: message)
}
funcmessageSent(message: Message){}
funcmessageChanged(message: Message){}
funcmessageDeleted(message: Message?){}
// MARK: Leaderboard Internal Logic
privatefunc listen(message: Message) {
// If a message contains our bots user ID and a recognized command, handle that command
if let id = client.authenticatedUser?.id, text = message.text {
iftext.lowercased().contains(query: Command.Leaderboard.rawValue) &&text.contains(query: id) {
handleCommand(command: .Leaderboard, channel: message.channel)
}
}
// If a message contains a trigger value, handle that trigger
ifmessage.text?.contains(query: Trigger.PlusPlus.rawValue) == true {
handleMessageWithTrigger(message: message, trigger: .PlusPlus)
}
ifmessage.text?.contains(query: Trigger.MinusMinus.rawValue) == true {
handleMessageWithTrigger(message: message, trigger: .MinusMinus)
}
}
// Text parsing can be messy when you don't have Foundation...
privatefunchandleMessageWithTrigger(message: Message, trigger: Trigger) {
if let text = message.text,
start = text.index(of: "@"),
end = text.index(of: trigger.rawValue) {
let string = String(text.characters[start...end].dropLast().dropFirst())
let users = client.users.values.filter{$0.id == self.userID(string: string)}
// If the receiver of the trigger is a user, use their user ID
ifusers.count> 0 {
letidString = userID(string: string)
initalizationForValue(dictionary: &leaderboard, value: idString)
scoringForValue(dictionary: &leaderboard, value: idString, trigger: trigger)
// Otherwise just store the receiver value as is
} else {
initalizationForValue(dictionary: &leaderboard, value: string)
scoringForValue(dictionary: &leaderboard, value: string, trigger: trigger)
}
}
}
// Handle recognized commands
privatefunchandleCommand(command: Command, channel:String?) {
switch command {
case .Leaderboard:
// Send message to the channel with the leaderboard attached
if let id = channel {
client.webAPI.sendMessage(channel:id, text: "Leaderboard", linkNames: true, attachments: [constructLeaderboardAttachment()], success: {(response) in
}, failure: { (error) in
print("Leaderboard failed to post due to error:(error)")
})
}
}
}
privatefuncinitalizationForValue(dictionary: inout [String: Int], value: String) {
if dictionary[value] == nil {
dictionary[value] = 0
}
}
privatefuncscoringForValue(dictionary: inout [String: Int], value: String, trigger: Trigger) {
switch trigger {
case .PlusPlus:
dictionary[value]?+=1
case .MinusMinus:
dictionary[value]?-=1
}
}
// MARK: Leaderboard Interface
privatefuncconstructLeaderboardAttachment() -> Attachment? {
let
Great! But we’ll need to replace the dummy API token with the real deal before anything will work.
We need to create a bot integration in Slack. You’ll need a Slack instance that you have administrator access to. If you don’t already have one of those to play with, go sign up. Slack is free for small teams:
Now that we have our API token, we’re ready to do some local testing. Back in Xcode, select the leaderbot command-line application target and run your bot (⌘+R). When we go and look at Slack, our leaderbot’s activity indicator should show that it’s online. It’s alive! To ensure that it’s working, we should give our helpful little bot some karma points:
@leaderbot++
And ask it to see the leaderboard:
@leaderbot leaderboard
Now that we’ve verified that our leaderboard bot works locally, it’s time to deploy it. We are deploying on Heroku, so if you don’t have an account, go and sign up for a free one.
First, we need to add a Procfile for Heroku. Back in the terminal, run:
echo slackbot: .build/debug/leaderbot > Procfile
Next, let’s check in our code:
git init
git add .
git commit -am’leaderbot powering up’
Finally, we’ll setup Heroku:
heroku login
heroku create --buildpack https://github.com/pvzig/heroku-buildpack-swift.git leaderbot
heroku git:remote -a leaderbot
git push heroku master
Once you push to master, you’ll see Heroku going through the process of building your application.
When the build is complete, all that’s left to do is to run our bot:
heroku run:detached slackbot
Like when we tested locally, our bot should become active and respond to our commands!
Congratulations, you’ve successfully built and deployed a Slack bot written in Swift onto a Linux server!
Jay: Pure-Swift JSON parser and formatterkylef’s Heroku buildpack for Swift
Open Swift: Open source cross project standards for Swift
SlackKit: A Slack client library
Zewo: Open source libraries for modern server software
The linux version of SlackKit should be considered an alpha release. It’s a fun tech demo to show what’s possible with Swift on the server, not something to be relied upon. Feel free to report issues you come across.
Peter Zignego is an iOS developer in Durham, North Carolina, USA. He writes at bytesized.co, tweets at @pvzig, and freelances at Launch Software.