Created
June 16, 2016 02:29
-
-
Save pvzig/648c13d7eb08aa3e6b63a0a98f44938a to your computer and use it in GitHub Desktop.
Sample Slack bot implementation in Swift
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
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