Skip to content

Instantly share code, notes, and snippets.

@pvzig
Created June 16, 2016 02:29
Show Gist options
  • Save pvzig/648c13d7eb08aa3e6b63a0a98f44938a to your computer and use it in GitHub Desktop.
Save pvzig/648c13d7eb08aa3e6b63a0a98f44938a to your computer and use it in GitHub Desktop.
Sample Slack bot implementation in Swift
import String
import SlackKit
class Leaderboard: MessageEventsDelegate {
// A dictionary to hold our leaderboard
var leaderboard: [String: Int] = [String: Int]()
let atSet = 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 {
case PlusPlus = "++"
case MinusMinus = "--"
}
// MARK: MessageEventsDelegate
// Listen to the messages that are coming in over the Slack RTM connection
func messageReceived(message: Message) {
listen(message: message)
}
func messageSent(message: Message){}
func messageChanged(message: Message){}
func messageDeleted(message: Message?){}
// MARK: Leaderboard Internal Logic
private func 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 {
if text.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
if message.text?.contains(query: Trigger.PlusPlus.rawValue) == true {
handleMessageWithTrigger(message: message, trigger: .PlusPlus)
}
if message.text?.contains(query: Trigger.MinusMinus.rawValue) == true {
handleMessageWithTrigger(message: message, trigger: .MinusMinus)
}
}
// Text parsing can be messy when you don't have Foundation...
private func handleMessageWithTrigger(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
if users.count > 0 {
let idString = 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
private func handleCommand(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)")
})
}
}
}
private func initalizationForValue(dictionary: inout [String: Int], value: String) {
if dictionary[value] == nil {
dictionary[value] = 0
}
}
private func scoringForValue(dictionary: inout [String: Int], value: String, trigger: Trigger) {
switch trigger {
case .PlusPlus:
dictionary[value]?+=1
case .MinusMinus:
dictionary[value]?-=1
}
}
// MARK: Leaderboard Interface
private func constructLeaderboardAttachment() -> Attachment? {
let 💯 = AttachmentField(title: "💯", value: swapIDsForNames(string: topItems(dictionary: &leaderboard)), short: true)
let 💩 = AttachmentField(title: "💩", value: swapIDsForNames(string: bottomItems(dictionary: &leaderboard)), short: true)
return Attachment(fallback: "Leaderboard", title: "Leaderboard", colorHex: AttachmentColor.Good.rawValue, text: "", fields: [💯, 💩])
}
private func topItems(dictionary: inout [String: Int]) -> String {
let sortedKeys = dictionary.keys.sorted(isOrderedBefore: ({dictionary[$0] > dictionary[$1]})).filter({dictionary[$0] > 0})
let sortedValues = dictionary.values.sorted(isOrderedBefore: {$0 > $1}).filter({$0 > 0})
return leaderboardString(keys: sortedKeys, values: sortedValues)
}
private func bottomItems(dictionary: inout [String: Int]) -> String {
let sortedKeys = dictionary.keys.sorted(isOrderedBefore: ({dictionary[$0] < dictionary[$1]})).filter({dictionary[$0] < 0})
let sortedValues = dictionary.values.sorted(isOrderedBefore: {$0 < $1}).filter({$0 < 0})
return leaderboardString(keys: sortedKeys, values: sortedValues)
}
private func leaderboardString(keys: [String], values: [Int]) -> String {
var returnValue = ""
for i in 0..<values.count {
returnValue += keys[i] + " (" + "\(values[i])" + ")\n"
}
return returnValue
}
// MARK: - Utilities
private func swapIDsForNames(string: String) -> String {
var returnString = string
for key in client.users.keys {
if let name = client.users[key]?.name {
if returnString.contains(query: key) {
returnString.replace(string: key, with: "@"+name)
}
}
}
return returnString
}
private func userID(string: String) -> String {
let alphanumericSet = CharacterSet(characters:
["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
"0","1","2","3","4","5","6","7","8","9"])
return string.trim(alphanumericSet.inverted)
}
}
// Initalize our Leaderboard class with a valid token and connect when main.swift is run
let leaderboard = Leaderboard(token: "xoxb-SLACK_API_TOKEN")
leaderboard.client.connect()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment