From 6653d934f64a8f0a81f124d9cf56250f88eba1e5 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Mon, 18 Jan 2016 17:09:31 -0500 Subject: [PATCH 01/26] Start implementing the Slack Web API --- SlackKit.xcodeproj/project.pbxproj | 12 ++++++ SlackKit/ClientActions.swift | 51 ++++++++++++++++++++++ SlackKit/NetworkInterface.swift | 48 +++++++++++++++++++++ SlackKit/Sources/Client.swift | 60 ++++++++------------------ SlackKit/Sources/EventDispatcher.swift | 2 +- SlackKit/Sources/User.swift | 4 +- SlackKit/WebAPI.swift | 51 ++++++++++++++++++++++ 7 files changed, 182 insertions(+), 46 deletions(-) create mode 100644 SlackKit/ClientActions.swift create mode 100644 SlackKit/NetworkInterface.swift create mode 100644 SlackKit/WebAPI.swift diff --git a/SlackKit.xcodeproj/project.pbxproj b/SlackKit.xcodeproj/project.pbxproj index 7c31cd0..9ea11da 100644 --- a/SlackKit.xcodeproj/project.pbxproj +++ b/SlackKit.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2688F9011C4D757300BEA0DA /* ClientActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2688F9001C4D757300BEA0DA /* ClientActions.swift */; }; 26BBA1941C398E3C00BF7225 /* Bot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1871C398E3C00BF7225 /* Bot.swift */; }; 26BBA1951C398E3C00BF7225 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1881C398E3C00BF7225 /* Channel.swift */; }; 26BBA1961C398E3C00BF7225 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1891C398E3C00BF7225 /* Client.swift */; }; @@ -20,6 +21,8 @@ 26BBA19E1C398E3C00BF7225 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1911C398E3C00BF7225 /* Types.swift */; }; 26BBA19F1C398E3C00BF7225 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1921C398E3C00BF7225 /* User.swift */; }; 26BBA1A01C398E3C00BF7225 /* UserGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1931C398E3C00BF7225 /* UserGroup.swift */; }; + 26FAF7C51C4D4DB300CC4EDE /* NetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAF7C41C4D4DB300CC4EDE /* NetworkInterface.swift */; }; + 26FAF7C71C4D516F00CC4EDE /* WebAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAF7C61C4D516F00CC4EDE /* WebAPI.swift */; }; FFE3AC870D1C42EF276CCA2D /* Pods_SlackKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 407A2ABFC0611867E2BE34D0 /* Pods_SlackKit.framework */; }; /* End PBXBuildFile section */ @@ -27,6 +30,7 @@ 26072A341BB48B3A00CD650C /* SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2661A6A41BBF62FF0026F67B /* SlackKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SlackKit.h; sourceTree = ""; }; 266E05F01BBF780C00840D76 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 2688F9001C4D757300BEA0DA /* ClientActions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientActions.swift; sourceTree = ""; }; 26BBA1871C398E3C00BF7225 /* Bot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bot.swift; path = Sources/Bot.swift; sourceTree = ""; }; 26BBA1881C398E3C00BF7225 /* Channel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Channel.swift; path = Sources/Channel.swift; sourceTree = ""; }; 26BBA1891C398E3C00BF7225 /* Client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Client.swift; path = Sources/Client.swift; sourceTree = ""; }; @@ -40,6 +44,8 @@ 26BBA1911C398E3C00BF7225 /* Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Types.swift; path = Sources/Types.swift; sourceTree = ""; }; 26BBA1921C398E3C00BF7225 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = User.swift; path = Sources/User.swift; sourceTree = ""; }; 26BBA1931C398E3C00BF7225 /* UserGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UserGroup.swift; path = Sources/UserGroup.swift; sourceTree = ""; }; + 26FAF7C41C4D4DB300CC4EDE /* NetworkInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkInterface.swift; sourceTree = ""; }; + 26FAF7C61C4D516F00CC4EDE /* WebAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebAPI.swift; sourceTree = ""; }; 407A2ABFC0611867E2BE34D0 /* Pods_SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4347F92F3932C96C23B10B2A /* Pods-SlackKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SlackKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-SlackKit/Pods-SlackKit.release.xcconfig"; sourceTree = ""; }; F59B6A12F1C4C4E24C58E1BF /* Pods-SlackKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SlackKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SlackKit/Pods-SlackKit.debug.xcconfig"; sourceTree = ""; }; @@ -90,16 +96,19 @@ 26BBA1871C398E3C00BF7225 /* Bot.swift */, 26BBA1881C398E3C00BF7225 /* Channel.swift */, 26BBA1891C398E3C00BF7225 /* Client.swift */, + 2688F9001C4D757300BEA0DA /* ClientActions.swift */, 26BBA18A1C398E3C00BF7225 /* Event.swift */, 26BBA18B1C398E3C00BF7225 /* EventDelegate.swift */, 26BBA18C1C398E3C00BF7225 /* EventDispatcher.swift */, 26BBA18D1C398E3C00BF7225 /* EventHandler.swift */, 26BBA18E1C398E3C00BF7225 /* File.swift */, 26BBA18F1C398E3C00BF7225 /* Message.swift */, + 26FAF7C41C4D4DB300CC4EDE /* NetworkInterface.swift */, 26BBA1901C398E3C00BF7225 /* Team.swift */, 26BBA1911C398E3C00BF7225 /* Types.swift */, 26BBA1921C398E3C00BF7225 /* User.swift */, 26BBA1931C398E3C00BF7225 /* UserGroup.swift */, + 26FAF7C61C4D516F00CC4EDE /* WebAPI.swift */, 2661A6A41BBF62FF0026F67B /* SlackKit.h */, 266E05F01BBF780C00840D76 /* Info.plist */, ); @@ -237,8 +246,11 @@ 26BBA1941C398E3C00BF7225 /* Bot.swift in Sources */, 26BBA19B1C398E3C00BF7225 /* File.swift in Sources */, 26BBA19C1C398E3C00BF7225 /* Message.swift in Sources */, + 26FAF7C71C4D516F00CC4EDE /* WebAPI.swift in Sources */, + 26FAF7C51C4D4DB300CC4EDE /* NetworkInterface.swift in Sources */, 26BBA19D1C398E3C00BF7225 /* Team.swift in Sources */, 26BBA1A01C398E3C00BF7225 /* UserGroup.swift in Sources */, + 2688F9011C4D757300BEA0DA /* ClientActions.swift in Sources */, 26BBA1981C398E3C00BF7225 /* EventDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SlackKit/ClientActions.swift b/SlackKit/ClientActions.swift new file mode 100644 index 0000000..36aece4 --- /dev/null +++ b/SlackKit/ClientActions.swift @@ -0,0 +1,51 @@ +// +// ClientActions.swift +// SlackKit +// +// Created by Peter Zignego on 1/18/16. +// Copyright © 2016 Launch Software LLC. All rights reserved. +// + +import Foundation + +extension Client { + + //MARK: - User & Channel + public func getChannelOrUserIdByName(name: String) -> String? { + if (name[name.startIndex] == "@") { + return getUserIdByName(name) + } else if (name[name.startIndex] == "C") { + return getChannelIDByName(name) + } + return nil + } + + public func getChannelIDByName(name: String) -> String? { + return channels.filter{$0.1.name == stripString(name)}.first?.0 + } + + public func getUserIdByName(name: String) -> String? { + return users.filter{$0.1.name == stripString(name)}.first?.0 + } + + public func getImIDForUserWithID(id: String, callback: (String?) -> Void) { + let ims = Client.sharedInstance.channels.filter{$0.1.isIM == true} + let channel = ims.filter{$0.1.user == id}.first + if let channel = channel, user = channel.1.user { + callback(user) + } else { + WebAPI.imOpen(id, callback: callback) + } + } + + //MARK: - Utilities + internal func stripString(var string: String) -> String { + if string[string.startIndex] == "@" { + string = string.substringFromIndex(string.startIndex.advancedBy(1)) + } else if string[string.startIndex] == "#" { + string = string.substringFromIndex(string.startIndex.advancedBy(1)) + } + return string + } + +} \ No newline at end of file diff --git a/SlackKit/NetworkInterface.swift b/SlackKit/NetworkInterface.swift new file mode 100644 index 0000000..689307a --- /dev/null +++ b/SlackKit/NetworkInterface.swift @@ -0,0 +1,48 @@ +// +// NetworkInterface.swift +// SlackKit +// +// Created by Peter Zignego on 1/18/16. +// Copyright © 2016 Launch Software LLC. All rights reserved. +// + +import Foundation + +public struct NetworkInterface { + + private static let apiUrl = "https://slack.com/api/" + private static let token = Client.sharedInstance.token + + static public func slackAPIRequest(endpoint: String, parameters: [String: AnyObject]?, json: ([String: AnyObject]) -> Void) { + var requestString = "\(apiUrl)\(endpoint)?token=\(token)" + if let params = parameters { + requestString = requestString + NetworkInterface.requestStringFromParameters(params) + } + let request = NSURLRequest(URL: NSURL(string: requestString)!) + NSURLSession.sharedSession().dataTaskWithRequest(request) { + (data, response, error) -> Void in + guard let data = data else { + return + } + do { + let result = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject] + if (result["ok"] as! Bool == true) { + json(result) + } else { + //TODO: ERROR + } + } catch _ { + print(error) + } + } + } + + static private func requestStringFromParameters(parameters: [String: AnyObject]) -> String { + var requestString = "" + for key in parameters.keys { + requestString = requestString + "&\(key)=\(parameters[key])" + } + + return requestString + } +} diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index 4070300..d231c72 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -50,42 +50,18 @@ public class Client: WebSocketDelegate { public var reactionEventsDelegate: ReactionEventsDelegate? public var teamEventsDelegate: TeamEventsDelegate? public var subteamEventsDelegate: SubteamEventsDelegate? + + internal var token = "SLACK_AUTH_TOKEN" - private var token = "SLACK_AUTH_TOKEN" public func setAuthToken(token: String) { self.token = token } - private var webSocket: WebSocket? - - required public init() { - - } - + internal var webSocket: WebSocket? + public static let sharedInstance = Client() - - //MARK: - Connection - public func connect() { - let request = NSURLRequest(URL: NSURL(string:"https://slack.com/api/rtm.start?token="+token)!) - NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.currentQueue()!) { - (response, data, error) -> Void in - guard let data = data else { - return - } - do { - let result = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject] - if (result["ok"] as! Bool == true) { - self.initialSetup(result) - let socketURL = NSURL(string: result["url"] as! String) - self.webSocket = WebSocket(url: socketURL!) - self.webSocket?.delegate = self - self.webSocket?.connect() - } - } catch _ { - print(error) - } - } - } + + required public init() {} //MARK: - Message send public func sendMessage(message: String, channelID: String) { @@ -131,7 +107,7 @@ public class Client: WebSocketDelegate { } //MARK: - Client setup - private func initialSetup(json: [String: AnyObject]) { + internal func initialSetup(json: [String: AnyObject]) { team = Team(team: json["team"] as? [String: AnyObject]) authenticatedUser = User(user: json["self"] as? [String: AnyObject]) authenticatedUser?.doNotDisturbStatus = DoNotDisturbStatus(status: json["dnd"] as? [String: AnyObject]) @@ -144,7 +120,7 @@ public class Client: WebSocketDelegate { enumerateSubteams(json["subteams"] as? [String: AnyObject]) } - private func enumerateUsers(users: [AnyObject]?) { + internal func enumerateUsers(users: [AnyObject]?) { if let users = users { for user in users { let u = User(user: user as? [String: AnyObject]) @@ -153,7 +129,7 @@ public class Client: WebSocketDelegate { } } - private func enumerateChannels(channels: [AnyObject]?) { + internal func enumerateChannels(channels: [AnyObject]?) { if let channels = channels { for channel in channels { let c = Channel(channel: channel as? [String: AnyObject]) @@ -162,7 +138,7 @@ public class Client: WebSocketDelegate { } } - private func enumerateGroups(groups: [AnyObject]?) { + internal func enumerateGroups(groups: [AnyObject]?) { if let groups = groups { for group in groups { let g = Channel(channel: group as? [String: AnyObject]) @@ -171,7 +147,7 @@ public class Client: WebSocketDelegate { } } - private func enumerateIMs(ims: [AnyObject]?) { + internal func enumerateIMs(ims: [AnyObject]?) { if let ims = ims { for im in ims { let i = Channel(channel: im as? [String: AnyObject]) @@ -180,7 +156,7 @@ public class Client: WebSocketDelegate { } } - private func enumerateMPIMs(mpims: [AnyObject]?) { + internal func enumerateMPIMs(mpims: [AnyObject]?) { if let mpims = mpims { for mpim in mpims { let m = Channel(channel: mpim as? [String: AnyObject]) @@ -189,7 +165,7 @@ public class Client: WebSocketDelegate { } } - private func enumerateBots(bots: [AnyObject]?) { + internal func enumerateBots(bots: [AnyObject]?) { if let bots = bots { for bot in bots { let b = Bot(bot: bot as? [String: AnyObject]) @@ -198,7 +174,7 @@ public class Client: WebSocketDelegate { } } - private func enumerateSubteams(subteams: [String: AnyObject]?) { + internal func enumerateSubteams(subteams: [String: AnyObject]?) { if let subteams = subteams { if let all = subteams["all"] as? [[String: AnyObject]] { for item in all { @@ -216,8 +192,7 @@ public class Client: WebSocketDelegate { } // MARK: - WebSocketDelegate - public func websocketDidConnect(socket: WebSocket) { - } + public func websocketDidConnect(socket: WebSocket) {} public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { connected = false @@ -240,7 +215,6 @@ public class Client: WebSocketDelegate { } } - public func websocketDidReceiveData(socket: WebSocket, data: NSData) { - } + public func websocketDidReceiveData(socket: WebSocket, data: NSData) {} -} +} \ No newline at end of file diff --git a/SlackKit/Sources/EventDispatcher.swift b/SlackKit/Sources/EventDispatcher.swift index d0a3acb..ea9833b 100644 --- a/SlackKit/Sources/EventDispatcher.swift +++ b/SlackKit/Sources/EventDispatcher.swift @@ -130,7 +130,7 @@ internal struct EventDispatcher { // Not implemented per Slack documentation. break case .TeamMigrationStarted: - Client.sharedInstance.connect() + WebAPI.connect() case .SubteamCreated, .SubteamUpdated: EventHandler.subteam(event) case .SubteamSelfAdded: diff --git a/SlackKit/Sources/User.swift b/SlackKit/Sources/User.swift index b29638f..46c68c8 100644 --- a/SlackKit/Sources/User.swift +++ b/SlackKit/Sources/User.swift @@ -71,7 +71,7 @@ public struct User { internal(set) public var timeZoneLabel: String? internal(set) public var timeZoneOffSet: Int? internal(set) public var preferences: [String: AnyObject]? - // Client use + // Client properties internal(set) public var userGroups: [String: String]? internal init?(user: [String: AnyObject]?) { @@ -100,4 +100,4 @@ public struct User { self.id = id self.isBot = nil } -} +} \ No newline at end of file diff --git a/SlackKit/WebAPI.swift b/SlackKit/WebAPI.swift new file mode 100644 index 0000000..129dbc9 --- /dev/null +++ b/SlackKit/WebAPI.swift @@ -0,0 +1,51 @@ +// +// WebAPI.swift +// SlackKit +// +// Created by Peter Zignego on 1/18/16. +// Copyright © 2016 Launch Software LLC. All rights reserved. +// + +import Foundation +import Starscream + +enum SlackWebAPI: String { + case RTMStart = "rtm.start" + case IMOpen = "im.open" +} + +public struct WebAPI { + + //MARK: - Connection + public static func connect() { + NetworkInterface.slackAPIRequest(SlackWebAPI.RTMStart.rawValue, parameters: nil) { + (response) -> Void in + Client.sharedInstance.initialSetup(response) + if let socketURL = response["url"] as? String { + let url = NSURL(string: socketURL) + Client.sharedInstance.webSocket = WebSocket(url: url!) + Client.sharedInstance.webSocket?.delegate = Client.sharedInstance + Client.sharedInstance.webSocket?.connect() + } + } + } + + //MARK: - IM + public static func imOpen(userID: String, callback: (String?) -> Void) { + NetworkInterface.slackAPIRequest(SlackWebAPI.IMOpen.rawValue, parameters: ["user":userID]) { + (response) -> Void in + if let channel = response["channel"] as? [String: AnyObject], id = channel["id"] as? String { + let exists = Client.sharedInstance.channels.filter{$0.0 == id}.count > 0 + if exists == true { + Client.sharedInstance.channels[id]?.isOpen = true + } else { + Client.sharedInstance.channels[id] = Channel(channel: channel) + } + + if let delegate = Client.sharedInstance.groupEventsDelegate { + delegate.groupOpened(Client.sharedInstance.channels[id]!) + } + } + } + } +} From 64a0b0e8dc2166e23cf26d95e20f0f9b2afc49cd Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Mon, 18 Jan 2016 20:11:33 -0500 Subject: [PATCH 02/26] Fix networking --- SlackKit.xcodeproj/project.pbxproj | 16 +++++++-------- ...ntActions.swift => ClientExtensions.swift} | 12 +++++------ SlackKit/NetworkInterface.swift | 20 ++++++++++--------- SlackKit/{WebAPI.swift => SlackWebAPI.swift} | 13 ++++++------ SlackKit/Sources/Client.swift | 6 +++++- SlackKit/Sources/EventDispatcher.swift | 2 +- 6 files changed, 38 insertions(+), 31 deletions(-) rename SlackKit/{ClientActions.swift => ClientExtensions.swift} (84%) rename SlackKit/{WebAPI.swift => SlackWebAPI.swift} (78%) diff --git a/SlackKit.xcodeproj/project.pbxproj b/SlackKit.xcodeproj/project.pbxproj index 9ea11da..871c701 100644 --- a/SlackKit.xcodeproj/project.pbxproj +++ b/SlackKit.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 2688F9011C4D757300BEA0DA /* ClientActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2688F9001C4D757300BEA0DA /* ClientActions.swift */; }; + 2688F9011C4D757300BEA0DA /* ClientExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2688F9001C4D757300BEA0DA /* ClientExtensions.swift */; }; 26BBA1941C398E3C00BF7225 /* Bot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1871C398E3C00BF7225 /* Bot.swift */; }; 26BBA1951C398E3C00BF7225 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1881C398E3C00BF7225 /* Channel.swift */; }; 26BBA1961C398E3C00BF7225 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1891C398E3C00BF7225 /* Client.swift */; }; @@ -22,7 +22,7 @@ 26BBA19F1C398E3C00BF7225 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1921C398E3C00BF7225 /* User.swift */; }; 26BBA1A01C398E3C00BF7225 /* UserGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1931C398E3C00BF7225 /* UserGroup.swift */; }; 26FAF7C51C4D4DB300CC4EDE /* NetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAF7C41C4D4DB300CC4EDE /* NetworkInterface.swift */; }; - 26FAF7C71C4D516F00CC4EDE /* WebAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAF7C61C4D516F00CC4EDE /* WebAPI.swift */; }; + 26FAF7C71C4D516F00CC4EDE /* SlackWebAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAF7C61C4D516F00CC4EDE /* SlackWebAPI.swift */; }; FFE3AC870D1C42EF276CCA2D /* Pods_SlackKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 407A2ABFC0611867E2BE34D0 /* Pods_SlackKit.framework */; }; /* End PBXBuildFile section */ @@ -30,7 +30,7 @@ 26072A341BB48B3A00CD650C /* SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2661A6A41BBF62FF0026F67B /* SlackKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SlackKit.h; sourceTree = ""; }; 266E05F01BBF780C00840D76 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 2688F9001C4D757300BEA0DA /* ClientActions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientActions.swift; sourceTree = ""; }; + 2688F9001C4D757300BEA0DA /* ClientExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientExtensions.swift; sourceTree = ""; }; 26BBA1871C398E3C00BF7225 /* Bot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bot.swift; path = Sources/Bot.swift; sourceTree = ""; }; 26BBA1881C398E3C00BF7225 /* Channel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Channel.swift; path = Sources/Channel.swift; sourceTree = ""; }; 26BBA1891C398E3C00BF7225 /* Client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Client.swift; path = Sources/Client.swift; sourceTree = ""; }; @@ -45,7 +45,7 @@ 26BBA1921C398E3C00BF7225 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = User.swift; path = Sources/User.swift; sourceTree = ""; }; 26BBA1931C398E3C00BF7225 /* UserGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UserGroup.swift; path = Sources/UserGroup.swift; sourceTree = ""; }; 26FAF7C41C4D4DB300CC4EDE /* NetworkInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkInterface.swift; sourceTree = ""; }; - 26FAF7C61C4D516F00CC4EDE /* WebAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebAPI.swift; sourceTree = ""; }; + 26FAF7C61C4D516F00CC4EDE /* SlackWebAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlackWebAPI.swift; sourceTree = ""; }; 407A2ABFC0611867E2BE34D0 /* Pods_SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4347F92F3932C96C23B10B2A /* Pods-SlackKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SlackKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-SlackKit/Pods-SlackKit.release.xcconfig"; sourceTree = ""; }; F59B6A12F1C4C4E24C58E1BF /* Pods-SlackKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SlackKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SlackKit/Pods-SlackKit.debug.xcconfig"; sourceTree = ""; }; @@ -96,7 +96,7 @@ 26BBA1871C398E3C00BF7225 /* Bot.swift */, 26BBA1881C398E3C00BF7225 /* Channel.swift */, 26BBA1891C398E3C00BF7225 /* Client.swift */, - 2688F9001C4D757300BEA0DA /* ClientActions.swift */, + 2688F9001C4D757300BEA0DA /* ClientExtensions.swift */, 26BBA18A1C398E3C00BF7225 /* Event.swift */, 26BBA18B1C398E3C00BF7225 /* EventDelegate.swift */, 26BBA18C1C398E3C00BF7225 /* EventDispatcher.swift */, @@ -108,7 +108,7 @@ 26BBA1911C398E3C00BF7225 /* Types.swift */, 26BBA1921C398E3C00BF7225 /* User.swift */, 26BBA1931C398E3C00BF7225 /* UserGroup.swift */, - 26FAF7C61C4D516F00CC4EDE /* WebAPI.swift */, + 26FAF7C61C4D516F00CC4EDE /* SlackWebAPI.swift */, 2661A6A41BBF62FF0026F67B /* SlackKit.h */, 266E05F01BBF780C00840D76 /* Info.plist */, ); @@ -246,11 +246,11 @@ 26BBA1941C398E3C00BF7225 /* Bot.swift in Sources */, 26BBA19B1C398E3C00BF7225 /* File.swift in Sources */, 26BBA19C1C398E3C00BF7225 /* Message.swift in Sources */, - 26FAF7C71C4D516F00CC4EDE /* WebAPI.swift in Sources */, + 26FAF7C71C4D516F00CC4EDE /* SlackWebAPI.swift in Sources */, 26FAF7C51C4D4DB300CC4EDE /* NetworkInterface.swift in Sources */, 26BBA19D1C398E3C00BF7225 /* Team.swift in Sources */, 26BBA1A01C398E3C00BF7225 /* UserGroup.swift in Sources */, - 2688F9011C4D757300BEA0DA /* ClientActions.swift in Sources */, + 2688F9011C4D757300BEA0DA /* ClientExtensions.swift in Sources */, 26BBA1981C398E3C00BF7225 /* EventDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SlackKit/ClientActions.swift b/SlackKit/ClientExtensions.swift similarity index 84% rename from SlackKit/ClientActions.swift rename to SlackKit/ClientExtensions.swift index 36aece4..2893e0f 100644 --- a/SlackKit/ClientActions.swift +++ b/SlackKit/ClientExtensions.swift @@ -1,5 +1,5 @@ // -// ClientActions.swift +// ClientExtensions.swift // SlackKit // // Created by Peter Zignego on 1/18/16. @@ -28,13 +28,13 @@ extension Client { return users.filter{$0.1.name == stripString(name)}.first?.0 } - public func getImIDForUserWithID(id: String, callback: (String?) -> Void) { + public func getImIDForUserWithID(id: String, completion: (imID: String?) -> Void) { let ims = Client.sharedInstance.channels.filter{$0.1.isIM == true} let channel = ims.filter{$0.1.user == id}.first - if let channel = channel, user = channel.1.user { - callback(user) + if let channel = channel { + completion(imID: channel.0) } else { - WebAPI.imOpen(id, callback: callback) + SlackWebAPI.imOpen(id, completion: completion) } } @@ -48,4 +48,4 @@ extension Client { return string } -} \ No newline at end of file +} diff --git a/SlackKit/NetworkInterface.swift b/SlackKit/NetworkInterface.swift index 689307a..9c1786b 100644 --- a/SlackKit/NetworkInterface.swift +++ b/SlackKit/NetworkInterface.swift @@ -10,13 +10,13 @@ import Foundation public struct NetworkInterface { - private static let apiUrl = "https://slack.com/api/" - private static let token = Client.sharedInstance.token + private let apiUrl = "https://slack.com/api/" - static public func slackAPIRequest(endpoint: String, parameters: [String: AnyObject]?, json: ([String: AnyObject]) -> Void) { - var requestString = "\(apiUrl)\(endpoint)?token=\(token)" + internal func request(endpoint: SlackAPIEndpoint, parameters: [String: AnyObject]?, json: ([String: AnyObject]) -> Void) { + let token = Client.sharedInstance.token + var requestString = "\(apiUrl)\(endpoint.rawValue)?token=\(token)" if let params = parameters { - requestString = requestString + NetworkInterface.requestStringFromParameters(params) + requestString = requestString + requestStringFromParameters(params) } let request = NSURLRequest(URL: NSURL(string: requestString)!) NSURLSession.sharedSession().dataTaskWithRequest(request) { @@ -29,18 +29,20 @@ public struct NetworkInterface { if (result["ok"] as! Bool == true) { json(result) } else { - //TODO: ERROR + //Error } } catch _ { print(error) } - } + }.resume() } - static private func requestStringFromParameters(parameters: [String: AnyObject]) -> String { + private func requestStringFromParameters(parameters: [String: AnyObject]) -> String { var requestString = "" for key in parameters.keys { - requestString = requestString + "&\(key)=\(parameters[key])" + if let value = parameters[key] as? String { + requestString = requestString + "&\(key)=\(value)" + } } return requestString diff --git a/SlackKit/WebAPI.swift b/SlackKit/SlackWebAPI.swift similarity index 78% rename from SlackKit/WebAPI.swift rename to SlackKit/SlackWebAPI.swift index 129dbc9..997991b 100644 --- a/SlackKit/WebAPI.swift +++ b/SlackKit/SlackWebAPI.swift @@ -1,5 +1,5 @@ // -// WebAPI.swift +// SlackWebAPI.swift // SlackKit // // Created by Peter Zignego on 1/18/16. @@ -9,16 +9,16 @@ import Foundation import Starscream -enum SlackWebAPI: String { +enum SlackAPIEndpoint: String { case RTMStart = "rtm.start" case IMOpen = "im.open" } -public struct WebAPI { +public struct SlackWebAPI { //MARK: - Connection public static func connect() { - NetworkInterface.slackAPIRequest(SlackWebAPI.RTMStart.rawValue, parameters: nil) { + Client.sharedInstance.api.request(SlackAPIEndpoint.RTMStart, parameters: nil) { (response) -> Void in Client.sharedInstance.initialSetup(response) if let socketURL = response["url"] as? String { @@ -31,8 +31,8 @@ public struct WebAPI { } //MARK: - IM - public static func imOpen(userID: String, callback: (String?) -> Void) { - NetworkInterface.slackAPIRequest(SlackWebAPI.IMOpen.rawValue, parameters: ["user":userID]) { + public static func imOpen(userID: String, completion: (imID: String?) -> Void) { + Client.sharedInstance.api.request(SlackAPIEndpoint.IMOpen, parameters: ["user":userID]) { (response) -> Void in if let channel = response["channel"] as? [String: AnyObject], id = channel["id"] as? String { let exists = Client.sharedInstance.channels.filter{$0.0 == id}.count > 0 @@ -41,6 +41,7 @@ public struct WebAPI { } else { Client.sharedInstance.channels[id] = Channel(channel: channel) } + completion(imID: id) if let delegate = Client.sharedInstance.groupEventsDelegate { delegate.groupOpened(Client.sharedInstance.channels[id]!) diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index d231c72..9160b94 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -58,11 +58,15 @@ public class Client: WebSocketDelegate { } internal var webSocket: WebSocket? - + internal let api = NetworkInterface() public static let sharedInstance = Client() required public init() {} + public func connect() { + SlackWebAPI.connect() + } + //MARK: - Message send public func sendMessage(message: String, channelID: String) { if (connected) { diff --git a/SlackKit/Sources/EventDispatcher.swift b/SlackKit/Sources/EventDispatcher.swift index ea9833b..d0a3acb 100644 --- a/SlackKit/Sources/EventDispatcher.swift +++ b/SlackKit/Sources/EventDispatcher.swift @@ -130,7 +130,7 @@ internal struct EventDispatcher { // Not implemented per Slack documentation. break case .TeamMigrationStarted: - WebAPI.connect() + Client.sharedInstance.connect() case .SubteamCreated, .SubteamUpdated: EventHandler.subteam(event) case .SubteamSelfAdded: From 02791a4ea0a50f873dd019a7b073c0dacf393e97 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Mon, 18 Jan 2016 20:19:33 -0500 Subject: [PATCH 03/26] Move into sources folder --- SlackKit.xcodeproj/project.pbxproj | 24 +++++++++---------- SlackKit/{ => Sources}/ClientExtensions.swift | 0 SlackKit/{ => Sources}/NetworkInterface.swift | 0 SlackKit/{ => Sources}/SlackWebAPI.swift | 0 4 files changed, 12 insertions(+), 12 deletions(-) rename SlackKit/{ => Sources}/ClientExtensions.swift (100%) rename SlackKit/{ => Sources}/NetworkInterface.swift (100%) rename SlackKit/{ => Sources}/SlackWebAPI.swift (100%) diff --git a/SlackKit.xcodeproj/project.pbxproj b/SlackKit.xcodeproj/project.pbxproj index 871c701..e6f559c 100644 --- a/SlackKit.xcodeproj/project.pbxproj +++ b/SlackKit.xcodeproj/project.pbxproj @@ -7,7 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 2688F9011C4D757300BEA0DA /* ClientExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2688F9001C4D757300BEA0DA /* ClientExtensions.swift */; }; + 260EC2331C4DC61D0093B253 /* ClientExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260EC2301C4DC61D0093B253 /* ClientExtensions.swift */; }; + 260EC2341C4DC61D0093B253 /* NetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260EC2311C4DC61D0093B253 /* NetworkInterface.swift */; }; + 260EC2351C4DC61D0093B253 /* SlackWebAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260EC2321C4DC61D0093B253 /* SlackWebAPI.swift */; }; 26BBA1941C398E3C00BF7225 /* Bot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1871C398E3C00BF7225 /* Bot.swift */; }; 26BBA1951C398E3C00BF7225 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1881C398E3C00BF7225 /* Channel.swift */; }; 26BBA1961C398E3C00BF7225 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1891C398E3C00BF7225 /* Client.swift */; }; @@ -21,16 +23,16 @@ 26BBA19E1C398E3C00BF7225 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1911C398E3C00BF7225 /* Types.swift */; }; 26BBA19F1C398E3C00BF7225 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1921C398E3C00BF7225 /* User.swift */; }; 26BBA1A01C398E3C00BF7225 /* UserGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1931C398E3C00BF7225 /* UserGroup.swift */; }; - 26FAF7C51C4D4DB300CC4EDE /* NetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAF7C41C4D4DB300CC4EDE /* NetworkInterface.swift */; }; - 26FAF7C71C4D516F00CC4EDE /* SlackWebAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FAF7C61C4D516F00CC4EDE /* SlackWebAPI.swift */; }; FFE3AC870D1C42EF276CCA2D /* Pods_SlackKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 407A2ABFC0611867E2BE34D0 /* Pods_SlackKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 26072A341BB48B3A00CD650C /* SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 260EC2301C4DC61D0093B253 /* ClientExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ClientExtensions.swift; path = Sources/ClientExtensions.swift; sourceTree = ""; }; + 260EC2311C4DC61D0093B253 /* NetworkInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NetworkInterface.swift; path = Sources/NetworkInterface.swift; sourceTree = ""; }; + 260EC2321C4DC61D0093B253 /* SlackWebAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SlackWebAPI.swift; path = Sources/SlackWebAPI.swift; sourceTree = ""; }; 2661A6A41BBF62FF0026F67B /* SlackKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SlackKit.h; sourceTree = ""; }; 266E05F01BBF780C00840D76 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 2688F9001C4D757300BEA0DA /* ClientExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientExtensions.swift; sourceTree = ""; }; 26BBA1871C398E3C00BF7225 /* Bot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bot.swift; path = Sources/Bot.swift; sourceTree = ""; }; 26BBA1881C398E3C00BF7225 /* Channel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Channel.swift; path = Sources/Channel.swift; sourceTree = ""; }; 26BBA1891C398E3C00BF7225 /* Client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Client.swift; path = Sources/Client.swift; sourceTree = ""; }; @@ -44,8 +46,6 @@ 26BBA1911C398E3C00BF7225 /* Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Types.swift; path = Sources/Types.swift; sourceTree = ""; }; 26BBA1921C398E3C00BF7225 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = User.swift; path = Sources/User.swift; sourceTree = ""; }; 26BBA1931C398E3C00BF7225 /* UserGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UserGroup.swift; path = Sources/UserGroup.swift; sourceTree = ""; }; - 26FAF7C41C4D4DB300CC4EDE /* NetworkInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkInterface.swift; sourceTree = ""; }; - 26FAF7C61C4D516F00CC4EDE /* SlackWebAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlackWebAPI.swift; sourceTree = ""; }; 407A2ABFC0611867E2BE34D0 /* Pods_SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4347F92F3932C96C23B10B2A /* Pods-SlackKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SlackKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-SlackKit/Pods-SlackKit.release.xcconfig"; sourceTree = ""; }; F59B6A12F1C4C4E24C58E1BF /* Pods-SlackKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SlackKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SlackKit/Pods-SlackKit.debug.xcconfig"; sourceTree = ""; }; @@ -96,19 +96,19 @@ 26BBA1871C398E3C00BF7225 /* Bot.swift */, 26BBA1881C398E3C00BF7225 /* Channel.swift */, 26BBA1891C398E3C00BF7225 /* Client.swift */, - 2688F9001C4D757300BEA0DA /* ClientExtensions.swift */, + 260EC2301C4DC61D0093B253 /* ClientExtensions.swift */, 26BBA18A1C398E3C00BF7225 /* Event.swift */, 26BBA18B1C398E3C00BF7225 /* EventDelegate.swift */, 26BBA18C1C398E3C00BF7225 /* EventDispatcher.swift */, 26BBA18D1C398E3C00BF7225 /* EventHandler.swift */, 26BBA18E1C398E3C00BF7225 /* File.swift */, 26BBA18F1C398E3C00BF7225 /* Message.swift */, - 26FAF7C41C4D4DB300CC4EDE /* NetworkInterface.swift */, + 260EC2311C4DC61D0093B253 /* NetworkInterface.swift */, 26BBA1901C398E3C00BF7225 /* Team.swift */, 26BBA1911C398E3C00BF7225 /* Types.swift */, 26BBA1921C398E3C00BF7225 /* User.swift */, 26BBA1931C398E3C00BF7225 /* UserGroup.swift */, - 26FAF7C61C4D516F00CC4EDE /* SlackWebAPI.swift */, + 260EC2321C4DC61D0093B253 /* SlackWebAPI.swift */, 2661A6A41BBF62FF0026F67B /* SlackKit.h */, 266E05F01BBF780C00840D76 /* Info.plist */, ); @@ -245,12 +245,12 @@ 26BBA1971C398E3C00BF7225 /* Event.swift in Sources */, 26BBA1941C398E3C00BF7225 /* Bot.swift in Sources */, 26BBA19B1C398E3C00BF7225 /* File.swift in Sources */, + 260EC2351C4DC61D0093B253 /* SlackWebAPI.swift in Sources */, 26BBA19C1C398E3C00BF7225 /* Message.swift in Sources */, - 26FAF7C71C4D516F00CC4EDE /* SlackWebAPI.swift in Sources */, - 26FAF7C51C4D4DB300CC4EDE /* NetworkInterface.swift in Sources */, 26BBA19D1C398E3C00BF7225 /* Team.swift in Sources */, + 260EC2331C4DC61D0093B253 /* ClientExtensions.swift in Sources */, 26BBA1A01C398E3C00BF7225 /* UserGroup.swift in Sources */, - 2688F9011C4D757300BEA0DA /* ClientExtensions.swift in Sources */, + 260EC2341C4DC61D0093B253 /* NetworkInterface.swift in Sources */, 26BBA1981C398E3C00BF7225 /* EventDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SlackKit/ClientExtensions.swift b/SlackKit/Sources/ClientExtensions.swift similarity index 100% rename from SlackKit/ClientExtensions.swift rename to SlackKit/Sources/ClientExtensions.swift diff --git a/SlackKit/NetworkInterface.swift b/SlackKit/Sources/NetworkInterface.swift similarity index 100% rename from SlackKit/NetworkInterface.swift rename to SlackKit/Sources/NetworkInterface.swift diff --git a/SlackKit/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift similarity index 100% rename from SlackKit/SlackWebAPI.swift rename to SlackKit/Sources/SlackWebAPI.swift From de7dbfb9df3ceede3776394ac3f0a8f1d92823c5 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Mon, 8 Feb 2016 18:37:26 -0500 Subject: [PATCH 04/26] API interface improvements --- SlackKit/Sources/NetworkInterface.swift | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/SlackKit/Sources/NetworkInterface.swift b/SlackKit/Sources/NetworkInterface.swift index 9c1786b..92fa3c7 100644 --- a/SlackKit/Sources/NetworkInterface.swift +++ b/SlackKit/Sources/NetworkInterface.swift @@ -12,7 +12,7 @@ public struct NetworkInterface { private let apiUrl = "https://slack.com/api/" - internal func request(endpoint: SlackAPIEndpoint, parameters: [String: AnyObject]?, json: ([String: AnyObject]) -> Void) { + internal func request(endpoint: SlackAPIEndpoint, parameters: [String: AnyObject]?, successClosure: ([String: AnyObject])->Void, errorClosure: (SlackError)->Void) { let token = Client.sharedInstance.token var requestString = "\(apiUrl)\(endpoint.rawValue)?token=\(token)" if let params = parameters { @@ -20,19 +20,27 @@ public struct NetworkInterface { } let request = NSURLRequest(URL: NSURL(string: requestString)!) NSURLSession.sharedSession().dataTaskWithRequest(request) { - (data, response, error) -> Void in + (data, response, internalError) -> Void in guard let data = data else { return } do { let result = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject] if (result["ok"] as! Bool == true) { - json(result) + successClosure(result) } else { - //Error + if let errorString = result["error"] as? String { + throw ErrorDispatcher.dispatch(errorString) + } else { + throw SlackError.UnknownError + } + } + } catch let error { + if let slackError = error as? SlackError { + errorClosure(slackError) + } else { + errorClosure(SlackError.UnknownError) } - } catch _ { - print(error) } }.resume() } From 12dcb791a8cd223a74e5a30f48d9875915b5d6b1 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Mon, 8 Feb 2016 18:37:39 -0500 Subject: [PATCH 05/26] Error handling scaffolding --- SlackKit/SlackWebAPIErrorDispatcher.swift | 174 ++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 SlackKit/SlackWebAPIErrorDispatcher.swift diff --git a/SlackKit/SlackWebAPIErrorDispatcher.swift b/SlackKit/SlackWebAPIErrorDispatcher.swift new file mode 100644 index 0000000..38b077d --- /dev/null +++ b/SlackKit/SlackWebAPIErrorDispatcher.swift @@ -0,0 +1,174 @@ +// +// SlackWebAPIErrorHandling.swift +// SlackKit +// +// Created by Peter Zignego on 1/21/16. +// Copyright © 2016 Launch Software LLC. All rights reserved. +// + +import Foundation + +internal enum SlackError: ErrorType { + case AlreadyArchived + case AccountInactive + case AlreadyPinned + case AlreadyReacted + case AlreadyStarred + case BadTimeStamp + case CantArchiveGeneral + case CantDeleteFile + case CantDeleteMessage + case CantUpdateMessage + case ChannelNotFound + case ComplianceExportsPreventDeletion + case EditWindowClosed + case FileCommentNotFound + case FileDeleted + case FileNotFound + case FileNotShared + case InvalidArrayArg + case InvalidAuth + case InvalidChannel + case InvalidName + case InvalidPresence + case InvalidTS + case InvalidTSLatest + case InvalidTSOldest + case IsArchived + case LastRAChannel + case MessageNotFound + case MessageTooLong + case MigrationInProgress + case NoItemSpecified + case NoReaction + case NoText + case NotAuthed + case NotEnoughUsers + case NotInChannel + case NotPinned + case NotStarred + case PermissionDenied + case PostingToGeneralChannelDenied + case RateLimited + case RequestTimeout + case RestrictedAction + case TooLong + case TooManyEmoji + case TooManyReactions + case TooManyUsers + case UnknownError + case UserDisabled + case UserDoesNotOwnChannel + case UserIsRestricted + case UserListNotSupplied + case UserNotFound + case UserNotVisible +} + +internal struct ErrorDispatcher { + + static func dispatch(error: String) -> SlackError { + switch error { + case "account_inactive": + return .AccountInactive + case "already_pinned": + return .AlreadyPinned + case "already_reacted": + return .AlreadyReacted + case "already_starred": + return .AlreadyStarred + case "bad_timestamp": + return .BadTimeStamp + case "cant_delete_file": + return .CantDeleteFile + case "cant_delete_message": + return .CantDeleteMessage + case "cant_update_message": + return .CantUpdateMessage + case "compliance_exports_prevent_deletion": + return .ComplianceExportsPreventDeletion + case "channel_not_found": + return .ChannelNotFound + case "edit_window_closed": + return .EditWindowClosed + case "file_comment_not_found": + return .FileCommentNotFound + case "file_deleted": + return .FileDeleted + case "file_not_found": + return .FileNotFound + case "file_not_shared": + return .FileNotShared + case "invalid_array_arg": + return .InvalidArrayArg + case "invalid_auth": + return .InvalidAuth + case "invalid_channel": + return .InvalidChannel + case "invalid_name": + return .InvalidName + case "invalid_presence": + return .InvalidPresence + case "invalid_timestamp": + return .InvalidTS + case "invalid_ts_latest": + return .InvalidTSLatest + case "invalid_ts_oldest": + return .InvalidTSOldest + case "is_archived": + return .IsArchived + case "message_not_found": + return .MessageNotFound + case "msg_too_long": + return .MessageTooLong + case "migration_in_progress": + return .MigrationInProgress + case "no_reaction": + return .NoReaction + case "no_item_specified": + return .NoItemSpecified + case "no_text": + return .NoText + case "not_authed": + return .NotAuthed + case "not_enough_users": + return .NotEnoughUsers + case "not_in_channel": + return .NotInChannel + case "not_pinned": + return .NotPinned + case "not_starred": + return .NotStarred + case "perimssion_denied": + return .PermissionDenied + case "posting_to_general_channel_denied": + return .PostingToGeneralChannelDenied + case "rate_limited": + return .RateLimited + case "request_timeout": + return .RequestTimeout + case "too_long": + return .TooLong + case "too_many_emoji": + return .TooManyEmoji + case "too_many_reactions": + return .TooManyReactions + case "too_many_users": + return .TooManyUsers + case "user_disabled": + return .UserDisabled + case "user_does_not_own_channel": + return .UserDoesNotOwnChannel + case "user_is_restricted": + return .UserIsRestricted + case "user_list_not_supplied": + return .UserListNotSupplied + case "user_not_found": + return .UserNotFound + case "user_not_visible": + return .UserNotVisible + default: + return .UnknownError + } + } +} From 074be89825f6f6f6128d78391355adddbc1ab75d Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Mon, 8 Feb 2016 18:38:23 -0500 Subject: [PATCH 06/26] Web API scaffolding --- SlackKit/Sources/SlackWebAPI.swift | 47 +++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index 997991b..c9bf057 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -10,8 +10,53 @@ import Foundation import Starscream enum SlackAPIEndpoint: String { - case RTMStart = "rtm.start" + case APITest = "api.test" + case AuthTest = "auth.test" + case ChannelsHistory = "channels.history" + case ChannelsInfo = "channels.info" + case ChannelsList = "channels.list" + case ChannelsMark = "channels.mark" + case ChannelsSetPurpose = "channels.setPurpose" + case ChannelsSetTopic = "channels.setTopic" + case ChatDelete = "chat.delete" + case ChatPostMessage = "chat.postMessage" + case ChatUpdate = "chat.update" + case EmojiList = "emoji.list" + case FilesDelete = "files.delete" + case FilesUpload = "files.upload" + case GroupsClose = "groups.close" + case GroupsHistory = "groups.history" + case GroupsInfo = "groups.info" + case GroupsList = "groups.list" + case GroupsMark = "groups.mark" + case GroupsOpen = "groups.open" + case GroupsSetPurpose = "groups.setPurpose" + case GroupsSetTopic = "groups.setTopic" + case IMClose = "im.close" + case IMHistory = "im.history" + case IMList = "im.list" + case IMMark = "im.mark" case IMOpen = "im.open" + case MPIMClose = "mpim.close" + case MPIMHistory = "mpim.history" + case MPIMList = "mpim.list" + case MPIMMark = "mpim.mark" + case MPIMOpen = "mpim.open" + case PinsAdd = "pins.add" + case PinsRemove = "pins.remove" + case ReactionsAdd = "reactions.add" + case ReactionsGet = "reactions.get" + case ReactionsList = "reactions.list" + case ReactionsRemove = "reactions.remove" + case RTMStart = "rtm.start" + case StarsAdd = "stars.add" + case StarsRemove = "stars.remove" + case TeamInfo = "team.info" + case UsersGetPresence = "users.getPresence" + case UsersInfo = "users.info" + case UsersList = "users.list" + case UsersSetActive = "users.setActive" + case UsersSetPresence = "users.setPresence" } public struct SlackWebAPI { From de3cf52687d59b9e1e2a67317a0e575d3b01349a Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sat, 13 Feb 2016 16:04:14 -0500 Subject: [PATCH 07/26] Web API partial implementation Need to update with error closures across all methods and client integration --- SlackKit/SlackWebAPIErrorDispatcher.swift | 23 +- SlackKit/Sources/Client.swift | 22 +- SlackKit/Sources/ClientExtensions.swift | 25 +- SlackKit/Sources/Event.swift | 4 +- SlackKit/Sources/EventDispatcher.swift | 119 ++--- SlackKit/Sources/EventHandler.swift | 342 +++++++------- SlackKit/Sources/NetworkInterface.swift | 27 +- SlackKit/Sources/SlackWebAPI.swift | 523 +++++++++++++++++++++- 8 files changed, 815 insertions(+), 270 deletions(-) diff --git a/SlackKit/SlackWebAPIErrorDispatcher.swift b/SlackKit/SlackWebAPIErrorDispatcher.swift index 38b077d..31e9670 100644 --- a/SlackKit/SlackWebAPIErrorDispatcher.swift +++ b/SlackKit/SlackWebAPIErrorDispatcher.swift @@ -1,14 +1,29 @@ // // SlackWebAPIErrorHandling.swift -// SlackKit // -// Created by Peter Zignego on 1/21/16. -// Copyright © 2016 Launch Software LLC. All rights reserved. +// Copyright © 2016 Peter Zignego. All rights reserved. // +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. import Foundation -internal enum SlackError: ErrorType { +public enum SlackError: ErrorType { case AlreadyArchived case AccountInactive case AlreadyPinned diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index 9160b94..33ee4e6 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -57,14 +57,24 @@ public class Client: WebSocketDelegate { self.token = token } + public var slackWebAPI: SlackWebAPI? + internal var webSocket: WebSocket? + private var dispatcher: EventDispatcher? + internal let api = NetworkInterface() - public static let sharedInstance = Client() - + required public init() {} public func connect() { - SlackWebAPI.connect() + dispatcher = EventDispatcher(client: self) + slackWebAPI = SlackWebAPI(client: self) + slackWebAPI?.connect(success: { + (connecting) -> Void in + + }, + failure: { (error) -> Void in + }) } //MARK: - Message send @@ -72,7 +82,7 @@ public class Client: WebSocketDelegate { if (connected) { if let data = formatMessageToSlackJsonString(msg: message, channel: channelID) { let string = NSString(data: data, encoding: NSUTF8StringEncoding) - self.webSocket?.writeString(string as! String) + webSocket?.writeString(string as! String) } } } @@ -212,7 +222,7 @@ public class Client: WebSocketDelegate { return } do { - try EventDispatcher.eventDispatcher(NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! [String: AnyObject]) + try dispatcher?.dispatch(NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! [String: AnyObject]) } catch _ { @@ -221,4 +231,4 @@ public class Client: WebSocketDelegate { public func websocketDidReceiveData(socket: WebSocket, data: NSData) {} -} \ No newline at end of file +} diff --git a/SlackKit/Sources/ClientExtensions.swift b/SlackKit/Sources/ClientExtensions.swift index 2893e0f..bed1e9f 100644 --- a/SlackKit/Sources/ClientExtensions.swift +++ b/SlackKit/Sources/ClientExtensions.swift @@ -1,10 +1,25 @@ // // ClientExtensions.swift -// SlackKit // -// Created by Peter Zignego on 1/18/16. -// Copyright © 2016 Launch Software LLC. All rights reserved. +// Copyright © 2016 Peter Zignego. All rights reserved. // +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. import Foundation @@ -29,12 +44,12 @@ extension Client { } public func getImIDForUserWithID(id: String, completion: (imID: String?) -> Void) { - let ims = Client.sharedInstance.channels.filter{$0.1.isIM == true} + let ims = channels.filter{$0.1.isIM == true} let channel = ims.filter{$0.1.user == id}.first if let channel = channel { completion(imID: channel.0) } else { - SlackWebAPI.imOpen(id, completion: completion) + slackWebAPI?.openIM(id, completion: completion) } } diff --git a/SlackKit/Sources/Event.swift b/SlackKit/Sources/Event.swift index 968a347..2de1024 100644 --- a/SlackKit/Sources/Event.swift +++ b/SlackKit/Sources/Event.swift @@ -138,7 +138,7 @@ internal struct Event { let domain: String? let emailDomain: String? let reaction: String? - let replyTo: String? + let replyTo: Double? let reactions: [[String: AnyObject]]? let edited: Edited? let bot: Bot? @@ -178,7 +178,7 @@ internal struct Event { domain = event["domain"] as? String emailDomain = event["email_domain"] as? String reaction = event["reaction"] as? String - replyTo = event["reply_to"] as? String + replyTo = event["reply_to"] as? Double reactions = event["reactions"] as? [[String: AnyObject]] bot = Bot(bot: event["bot"] as? [String: AnyObject]) edited = Edited(edited:event["edited"] as? [String: AnyObject]) diff --git a/SlackKit/Sources/EventDispatcher.swift b/SlackKit/Sources/EventDispatcher.swift index d0a3acb..53b6a07 100644 --- a/SlackKit/Sources/EventDispatcher.swift +++ b/SlackKit/Sources/EventDispatcher.swift @@ -21,122 +21,129 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -internal struct EventDispatcher { +internal class EventDispatcher { + let client: Client + let handler: EventHandler - static func eventDispatcher(event: [String: AnyObject]) { + required init(client: Client) { + self.client = client + handler = EventHandler(client: client) + } + + func dispatch(event: [String: AnyObject]) { let event = Event(event: event) if let type = event.type { switch type { case .Hello: - EventHandler.connected() + handler.connected() case .Ok: - EventHandler.messageSent(event) + handler.messageSent(event) case .Message: if (event.subtype != nil) { messageDispatcher(event) } else { - EventHandler.messageReceived(event) + handler.messageReceived(event) } case .UserTyping: - EventHandler.userTyping(event) + handler.userTyping(event) case .ChannelMarked, .IMMarked, .GroupMarked: - EventHandler.channelMarked(event) + handler.channelMarked(event) case .ChannelCreated, .IMCreated: - EventHandler.channelCreated(event) + handler.channelCreated(event) case .ChannelJoined, .GroupJoined: - EventHandler.channelJoined(event) + handler.channelJoined(event) case .ChannelLeft, .GroupLeft: - EventHandler.channelLeft(event) + handler.channelLeft(event) case .ChannelDeleted: - EventHandler.channelDeleted(event) + handler.channelDeleted(event) case .ChannelRenamed, .GroupRename: - EventHandler.channelRenamed(event) + handler.channelRenamed(event) case .ChannelArchive, .GroupArchive: - EventHandler.channelArchived(event, archived: true) + handler.channelArchived(event, archived: true) case .ChannelUnarchive, .GroupUnarchive: - EventHandler.channelArchived(event, archived: false) + handler.channelArchived(event, archived: false) case .ChannelHistoryChanged, .IMHistoryChanged, .GroupHistoryChanged: - EventHandler.channelHistoryChanged(event) + handler.channelHistoryChanged(event) case .DNDUpdated: - EventHandler.doNotDisturbUpdated(event) + handler.doNotDisturbUpdated(event) case .DNDUpatedUser: - EventHandler.doNotDisturbUserUpdated(event) + handler.doNotDisturbUserUpdated(event) case .IMOpen, .GroupOpen: - EventHandler.open(event, open: true) + handler.open(event, open: true) case .IMClose, .GroupClose: - EventHandler.open(event, open: false) + handler.open(event, open: false) case .FileCreated: - EventHandler.processFile(event) + handler.processFile(event) case .FileShared: - EventHandler.processFile(event) + handler.processFile(event) case .FileUnshared: - EventHandler.processFile(event) + handler.processFile(event) case .FilePublic: - EventHandler.processFile(event) + handler.processFile(event) case .FilePrivate: - EventHandler.filePrivate(event) + handler.filePrivate(event) case .FileChanged: - EventHandler.processFile(event) + handler.processFile(event) case .FileDeleted: - EventHandler.deleteFile(event) + handler.deleteFile(event) case .FileCommentAdded: - EventHandler.fileCommentAdded(event) + handler.fileCommentAdded(event) case .FileCommentEdited: - EventHandler.fileCommentEdited(event) + handler.fileCommentEdited(event) case .FileCommentDeleted: - EventHandler.fileCommentDeleted(event) + handler.fileCommentDeleted(event) case .PinAdded: - EventHandler.pinAdded(event) + handler.pinAdded(event) case .PinRemoved: - EventHandler.pinRemoved(event) + handler.pinRemoved(event) case .PresenceChange: - EventHandler.presenceChange(event) + handler.presenceChange(event) case .ManualPresenceChange: - EventHandler.manualPresenceChange(event) + handler.manualPresenceChange(event) case .PrefChange: - EventHandler.changePreference(event) + handler.changePreference(event) case .UserChange: - EventHandler.userChange(event) + handler.userChange(event) case .TeamJoin: - EventHandler.teamJoin(event) + handler.teamJoin(event) case .StarAdded: - EventHandler.itemStarred(event, star: true) + handler.itemStarred(event, star: true) case .StarRemoved: - EventHandler.itemStarred(event, star: false) + handler.itemStarred(event, star: false) case .ReactionAdded: - EventHandler.addedReaction(event) + handler.addedReaction(event) case .ReactionRemoved: - EventHandler.removedReaction(event) + handler.removedReaction(event) case .EmojiChanged: - EventHandler.emojiChanged(event) + handler.emojiChanged(event) case .CommandsChanged: // Not implemented per Slack documentation. break case .TeamPlanChange: - EventHandler.teamPlanChange(event) + handler.teamPlanChange(event) case .TeamPrefChange: - EventHandler.teamPreferenceChange(event) + handler.teamPreferenceChange(event) case .TeamRename: - EventHandler.teamNameChange(event) + handler.teamNameChange(event) case .TeamDomainChange: - EventHandler.teamDomainChange(event) + handler.teamDomainChange(event) case .EmailDomainChange: - EventHandler.emailDomainChange(event) + handler.emailDomainChange(event) case .BotAdded: - EventHandler.bot(event) + handler.bot(event) case .BotChanged: - EventHandler.bot(event) + handler.bot(event) case .AccountsChanged: // Not implemented per Slack documentation. break case .TeamMigrationStarted: - Client.sharedInstance.connect() + client.connect() case .SubteamCreated, .SubteamUpdated: - EventHandler.subteam(event) + handler.subteam(event) case .SubteamSelfAdded: - EventHandler.subteamAddedSelf(event) + handler.subteamAddedSelf(event) case.SubteamSelfRemoved: - EventHandler.subteamRemovedSelf(event) + handler.subteamRemovedSelf(event) case .Error: print("Error: \(event)") break @@ -144,15 +151,15 @@ internal struct EventDispatcher { } } - static func messageDispatcher(event:Event) { + func messageDispatcher(event:Event) { let subtype = MessageSubtype(rawValue: event.subtype!)! switch subtype { case .MessageChanged: - EventHandler.messageChanged(event) + handler.messageChanged(event) case .MessageDeleted: - EventHandler.messageDeleted(event) + handler.messageDeleted(event) default: - EventHandler.messageReceived(event) + handler.messageReceived(event) } } diff --git a/SlackKit/Sources/EventHandler.swift b/SlackKit/Sources/EventHandler.swift index 89648a8..09c85ca 100644 --- a/SlackKit/Sources/EventHandler.swift +++ b/SlackKit/Sources/EventHandler.swift @@ -23,70 +23,74 @@ import Foundation -internal struct EventHandler { +internal class EventHandler { + let client: Client + required init(client: Client) { + self.client = client + } //MARK: - Initial connection - static func connected() { - Client.sharedInstance.connected = true + func connected() { + client.connected = true - if let delegate = Client.sharedInstance.slackEventsDelegate { + if let delegate = client.slackEventsDelegate { delegate.clientConnected() } } //MARK: - Messages - static func messageSent(event: Event) { - if let reply = event.replyTo, message = Client.sharedInstance.sentMessages[reply], channel = message.channel, ts = message.ts { + func messageSent(event: Event) { + if let reply = event.replyTo, message = client.sentMessages[NSNumber(double: reply).stringValue], channel = message.channel, ts = message.ts { message.ts = event.ts message.text = event.text - Client.sharedInstance.channels[channel]?.messages[ts] = message + client.channels[channel]?.messages[ts] = message - if let delegate = Client.sharedInstance.messageEventsDelegate { + if let delegate = client.messageEventsDelegate { delegate.messageSent(message) } } } - static func messageReceived(event: Event) { + func messageReceived(event: Event) { if let channel = event.channel, message = event.message, id = channel.id, ts = message.ts { - Client.sharedInstance.channels[id]?.messages[ts] = message + client.channels[id]?.messages[ts] = message - if let delegate = Client.sharedInstance.messageEventsDelegate { + if let delegate = client.messageEventsDelegate { delegate.messageReceived(message) } } } - static func messageChanged(event: Event) { + func messageChanged(event: Event) { if let id = event.channel?.id, nested = event.nestedMessage, ts = nested.ts { - Client.sharedInstance.channels[id]?.messages[ts] = nested + client.channels[id]?.messages[ts] = nested - if let delegate = Client.sharedInstance.messageEventsDelegate { + if let delegate = client.messageEventsDelegate { delegate.messageChanged(nested) } } } - static func messageDeleted(event: Event) { + func messageDeleted(event: Event) { if let id = event.channel?.id, key = event.message?.deletedTs { - let message = Client.sharedInstance.channels[id]?.messages[key] - Client.sharedInstance.channels[id]?.messages.removeValueForKey(key) + let message = client.channels[id]?.messages[key] + client.channels[id]?.messages.removeValueForKey(key) - if let delegate = Client.sharedInstance.messageEventsDelegate { + if let delegate = client.messageEventsDelegate { delegate.messageDeleted(message) } } } //MARK: - Channels - static func userTyping(event: Event) { + func userTyping(event: Event) { if let channelID = event.channel?.id, userID = event.user?.id { - if let _ = Client.sharedInstance.channels[channelID] { - if (!Client.sharedInstance.channels[channelID]!.usersTyping.contains(userID)) { - Client.sharedInstance.channels[channelID]?.usersTyping.append(userID) + if let _ = client.channels[channelID] { + if (!client.channels[channelID]!.usersTyping.contains(userID)) { + client.channels[channelID]?.usersTyping.append(userID) - if let delegate = Client.sharedInstance.channelEventsDelegate { + if let delegate = client.channelEventsDelegate { delegate.userTyping(event.channel, user: event.user) } } @@ -94,222 +98,222 @@ internal struct EventHandler { let timeout = dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC))) dispatch_after(timeout, dispatch_get_main_queue()) { - if let index = Client.sharedInstance.channels[channelID]?.usersTyping.indexOf(userID) { - Client.sharedInstance.channels[channelID]?.usersTyping.removeAtIndex(index) + if let index = self.client.channels[channelID]?.usersTyping.indexOf(userID) { + self.client.channels[channelID]?.usersTyping.removeAtIndex(index) } } } } - static func channelMarked(event: Event) { + func channelMarked(event: Event) { if let channel = event.channel, id = channel.id { - Client.sharedInstance.channels[id]?.lastRead = event.ts + client.channels[id]?.lastRead = event.ts - if let delegate = Client.sharedInstance.channelEventsDelegate { + if let delegate = client.channelEventsDelegate { delegate.channelMarked(channel, timestamp: event.ts) } } //TODO: Recalculate unreads } - static func channelCreated(event: Event) { + func channelCreated(event: Event) { if let channel = event.channel, id = channel.id { - Client.sharedInstance.channels[id] = channel + client.channels[id] = channel - if let delegate = Client.sharedInstance.channelEventsDelegate { + if let delegate = client.channelEventsDelegate { delegate.channelCreated(channel) } } } - static func channelDeleted(event: Event) { + func channelDeleted(event: Event) { if let channel = event.channel, id = channel.id { - Client.sharedInstance.channels.removeValueForKey(id) + client.channels.removeValueForKey(id) - if let delegate = Client.sharedInstance.channelEventsDelegate { + if let delegate = client.channelEventsDelegate { delegate.channelDeleted(channel) } } } - static func channelJoined(event: Event) { + func channelJoined(event: Event) { if let channel = event.channel, id = channel.id { - Client.sharedInstance.channels[id] = event.channel + client.channels[id] = event.channel - if let delegate = Client.sharedInstance.channelEventsDelegate { + if let delegate = client.channelEventsDelegate { delegate.channelJoined(channel) } } } - static func channelLeft(event: Event) { - if let channel = event.channel, id = channel.id, userID = Client.sharedInstance.authenticatedUser?.id { - if let index = Client.sharedInstance.channels[id]?.members.indexOf(userID) { - Client.sharedInstance.channels[id]?.members.removeAtIndex(index) + func channelLeft(event: Event) { + if let channel = event.channel, id = channel.id, userID = client.authenticatedUser?.id { + if let index = client.channels[id]?.members.indexOf(userID) { + client.channels[id]?.members.removeAtIndex(index) - if let delegate = Client.sharedInstance.channelEventsDelegate { + if let delegate = client.channelEventsDelegate { delegate.channelLeft(channel) } } } } - static func channelRenamed(event: Event) { + func channelRenamed(event: Event) { if let channel = event.channel, id = channel.id { - Client.sharedInstance.channels[id]?.name = channel.name + client.channels[id]?.name = channel.name - if let delegate = Client.sharedInstance.channelEventsDelegate { + if let delegate = client.channelEventsDelegate { delegate.channelRenamed(channel) } } } - static func channelArchived(event: Event, archived: Bool) { + func channelArchived(event: Event, archived: Bool) { if let channel = event.channel, id = channel.id { - Client.sharedInstance.channels[id]?.isArchived = archived + client.channels[id]?.isArchived = archived - if let delegate = Client.sharedInstance.channelEventsDelegate { + if let delegate = client.channelEventsDelegate { delegate.channelArchived(channel) } } } - static func channelHistoryChanged(event: Event) { + func channelHistoryChanged(event: Event) { if let channel = event.channel { //TODO: Reload chat history if there are any cached messages before latest - if let delegate = Client.sharedInstance.channelEventsDelegate { + if let delegate = client.channelEventsDelegate { delegate.channelHistoryChanged(channel) } } } //MARK: - Do Not Disturb - static func doNotDisturbUpdated(event: Event) { + func doNotDisturbUpdated(event: Event) { if let dndStatus = event.dndStatus { - Client.sharedInstance.authenticatedUser?.doNotDisturbStatus = dndStatus + client.authenticatedUser?.doNotDisturbStatus = dndStatus - if let delegate = Client.sharedInstance.doNotDisturbEventsDelegate { + if let delegate = client.doNotDisturbEventsDelegate { delegate.doNotDisturbUpdated(dndStatus) } } } - static func doNotDisturbUserUpdated(event: Event) { + func doNotDisturbUserUpdated(event: Event) { if let dndStatus = event.dndStatus, user = event.user, id = user.id { - Client.sharedInstance.users[id]?.doNotDisturbStatus = dndStatus + client.users[id]?.doNotDisturbStatus = dndStatus - if let delegate = Client.sharedInstance.doNotDisturbEventsDelegate { + if let delegate = client.doNotDisturbEventsDelegate { delegate.doNotDisturbUserUpdated(dndStatus, user: user) } } } //MARK: - IM & Group Open/Close - static func open(event: Event, open: Bool) { + func open(event: Event, open: Bool) { if let channel = event.channel, id = channel.id { - Client.sharedInstance.channels[id]?.isOpen = open + client.channels[id]?.isOpen = open - if let delegate = Client.sharedInstance.groupEventsDelegate { + if let delegate = client.groupEventsDelegate { delegate.groupOpened(channel) } } } //MARK: - Files - static func processFile(event: Event) { + func processFile(event: Event) { if let file = event.file, id = file.id { if let comment = file.initialComment, commentID = comment.id { - if Client.sharedInstance.files[id]?.comments[commentID] == nil { - Client.sharedInstance.files[id]?.comments[commentID] = comment + if client.files[id]?.comments[commentID] == nil { + client.files[id]?.comments[commentID] = comment } } - Client.sharedInstance.files[id] = file + client.files[id] = file - if let delegate = Client.sharedInstance.fileEventsDelegate { + if let delegate = client.fileEventsDelegate { delegate.fileProcessed(file) } } } - static func filePrivate(event: Event) { + func filePrivate(event: Event) { if let file = event.file, id = file.id { - Client.sharedInstance.files[id]?.isPublic = false + client.files[id]?.isPublic = false - if let delegate = Client.sharedInstance.fileEventsDelegate { + if let delegate = client.fileEventsDelegate { delegate.fileMadePrivate(file) } } } - static func deleteFile(event: Event) { + func deleteFile(event: Event) { if let file = event.file, id = file.id { - if Client.sharedInstance.files[id] != nil { - Client.sharedInstance.files.removeValueForKey(id) + if client.files[id] != nil { + client.files.removeValueForKey(id) } - if let delegate = Client.sharedInstance.fileEventsDelegate { + if let delegate = client.fileEventsDelegate { delegate.fileDeleted(file) } } } - static func fileCommentAdded(event: Event) { + func fileCommentAdded(event: Event) { if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id { - Client.sharedInstance.files[id]?.comments[commentID] = comment + client.files[id]?.comments[commentID] = comment - if let delegate = Client.sharedInstance.fileEventsDelegate { + if let delegate = client.fileEventsDelegate { delegate.fileCommentAdded(file, comment: comment) } } } - static func fileCommentEdited(event: Event) { + func fileCommentEdited(event: Event) { if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id { - Client.sharedInstance.files[id]?.comments[commentID]?.comment = comment.comment + client.files[id]?.comments[commentID]?.comment = comment.comment - if let delegate = Client.sharedInstance.fileEventsDelegate { + if let delegate = client.fileEventsDelegate { delegate.fileCommentEdited(file, comment: comment) } } } - static func fileCommentDeleted(event: Event) { + func fileCommentDeleted(event: Event) { if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id { - Client.sharedInstance.files[id]?.comments.removeValueForKey(commentID) + client.files[id]?.comments.removeValueForKey(commentID) - if let delegate = Client.sharedInstance.fileEventsDelegate { + if let delegate = client.fileEventsDelegate { delegate.fileCommentDeleted(file, comment: comment) } } } //MARK: - Pins - static func pinAdded(event: Event) { + func pinAdded(event: Event) { if let id = event.channelID, item = event.item { - Client.sharedInstance.channels[id]?.pinnedItems.append(item) + client.channels[id]?.pinnedItems.append(item) - if let delegate = Client.sharedInstance.pinEventsDelegate { - delegate.itemPinned(item, channel: Client.sharedInstance.channels[id]) + if let delegate = client.pinEventsDelegate { + delegate.itemPinned(item, channel: client.channels[id]) } } } - static func pinRemoved(event: Event) { + func pinRemoved(event: Event) { if let id = event.channelID { - if let pins = Client.sharedInstance.channels[id]?.pinnedItems.filter({$0 != event.item}) { - Client.sharedInstance.channels[id]?.pinnedItems = pins + if let pins = client.channels[id]?.pinnedItems.filter({$0 != event.item}) { + client.channels[id]?.pinnedItems = pins } - if let delegate = Client.sharedInstance.pinEventsDelegate { - delegate.itemUnpinned(event.item, channel: Client.sharedInstance.channels[id]) + if let delegate = client.pinEventsDelegate { + delegate.itemUnpinned(event.item, channel: client.channels[id]) } } } //MARK: - Stars - static func itemStarred(event: Event, star: Bool) { + func itemStarred(event: Event, star: Bool) { if let item = event.item, type = item.type { switch type { case "message": @@ -322,48 +326,48 @@ internal struct EventHandler { break } - if let delegate = Client.sharedInstance.starEventsDelegate { + if let delegate = client.starEventsDelegate { delegate.itemStarred(item, star: star) } } } - static func starMessage(item: Item, star: Bool) { + func starMessage(item: Item, star: Bool) { if let message = item.message, ts = message.ts, channel = item.channel { - if let _ = Client.sharedInstance.channels[channel]?.messages[ts] { - Client.sharedInstance.channels[channel]?.messages[ts]?.isStarred = star + if let _ = client.channels[channel]?.messages[ts] { + client.channels[channel]?.messages[ts]?.isStarred = star } } } - static func starFile(item: Item, star: Bool) { + func starFile(item: Item, star: Bool) { if let file = item.file, id = file.id { - Client.sharedInstance.files[id]?.isStarred = star - if let stars = Client.sharedInstance.files[id]?.stars { + client.files[id]?.isStarred = star + if let stars = client.files[id]?.stars { if star == true { - Client.sharedInstance.files[id]?.stars = stars + 1 + client.files[id]?.stars = stars + 1 } else { if stars > 0 { - Client.sharedInstance.files[id]?.stars = stars - 1 + client.files[id]?.stars = stars - 1 } } } } } - static func starComment(item: Item) { + func starComment(item: Item) { if let file = item.file, id = file.id, comment = item.comment, commentID = comment.id { - Client.sharedInstance.files[id]?.comments[commentID] = comment + client.files[id]?.comments[commentID] = comment } } //MARK: - Reactions - static func addedReaction(event: Event) { + func addedReaction(event: Event) { if let item = event.item, type = item.type, key = event.reaction, userID = event.user?.id { switch type { case "message": if let channel = item.channel, ts = item.ts { - if let message = Client.sharedInstance.channels[channel]?.messages[ts] { + if let message = client.channels[channel]?.messages[ts] { if (message.reactions[key]) == nil { message.reactions[key] = Reaction(name: event.reaction, user: userID) } else { @@ -372,19 +376,19 @@ internal struct EventHandler { } } case "file": - if let id = item.file?.id, file = Client.sharedInstance.files[id] { + if let id = item.file?.id, file = client.files[id] { if file.reactions[key] == nil { - Client.sharedInstance.files[id]?.reactions[key] = Reaction(name: event.reaction, user: userID) + client.files[id]?.reactions[key] = Reaction(name: event.reaction, user: userID) } else { - Client.sharedInstance.files[id]?.reactions[key]?.users[userID] = userID + client.files[id]?.reactions[key]?.users[userID] = userID } } case "file_comment": - if let id = item.file?.id, file = Client.sharedInstance.files[id], commentID = item.fileCommentID { + if let id = item.file?.id, file = client.files[id], commentID = item.fileCommentID { if file.comments[commentID]?.reactions[key] == nil { - Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key] = Reaction(name: event.reaction, user: userID) + client.files[id]?.comments[commentID]?.reactions[key] = Reaction(name: event.reaction, user: userID) } else { - Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key]?.users[userID] = userID + client.files[id]?.comments[commentID]?.reactions[key]?.users[userID] = userID } } break @@ -392,18 +396,18 @@ internal struct EventHandler { break } - if let delegate = Client.sharedInstance.reactionEventsDelegate { + if let delegate = client.reactionEventsDelegate { delegate.reactionAdded(event.reaction, item: event.item) } } } - static func removedReaction(event: Event) { + func removedReaction(event: Event) { if let item = event.item, type = item.type, key = event.reaction, userID = event.user?.id { switch type { case "message": if let channel = item.channel, ts = item.ts { - if let message = Client.sharedInstance.channels[channel]?.messages[ts] { + if let message = client.channels[channel]?.messages[ts] { if (message.reactions[key]) != nil { message.reactions[key]?.users.removeValueForKey(userID) } @@ -413,21 +417,21 @@ internal struct EventHandler { } } case "file": - if let itemFile = item.file, id = itemFile.id, file = Client.sharedInstance.files[id] { + if let itemFile = item.file, id = itemFile.id, file = client.files[id] { if file.reactions[key] != nil { - Client.sharedInstance.files[id]?.reactions[key]?.users.removeValueForKey(userID) + client.files[id]?.reactions[key]?.users.removeValueForKey(userID) } - if Client.sharedInstance.files[id]?.reactions[key]?.users.count == 0 { - Client.sharedInstance.files[id]?.reactions.removeValueForKey(key) + if client.files[id]?.reactions[key]?.users.count == 0 { + client.files[id]?.reactions.removeValueForKey(key) } } case "file_comment": - if let id = item.file?.id, file = Client.sharedInstance.files[id], commentID = item.fileCommentID { + if let id = item.file?.id, file = client.files[id], commentID = item.fileCommentID { if file.comments[commentID]?.reactions[key] != nil { - Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key]?.users.removeValueForKey(userID) + client.files[id]?.comments[commentID]?.reactions[key]?.users.removeValueForKey(userID) } - if Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key]?.users.count == 0 { - Client.sharedInstance.files[id]?.comments[commentID]?.reactions.removeValueForKey(key) + if client.files[id]?.comments[commentID]?.reactions[key]?.users.count == 0 { + client.files[id]?.comments[commentID]?.reactions.removeValueForKey(key) } } break @@ -435,165 +439,165 @@ internal struct EventHandler { break } - if let delegate = Client.sharedInstance.reactionEventsDelegate { + if let delegate = client.reactionEventsDelegate { delegate.reactionAdded(event.reaction, item: event.item) } } } //MARK: - Preferences - static func changePreference(event: Event) { + func changePreference(event: Event) { if let name = event.name { - Client.sharedInstance.authenticatedUser?.preferences?[name] = event.value + client.authenticatedUser?.preferences?[name] = event.value - if let delegate = Client.sharedInstance.slackEventsDelegate, value = event.value { + if let delegate = client.slackEventsDelegate, value = event.value { delegate.preferenceChanged(name, value: value) } } } //Mark: - User Change - static func userChange(event: Event) { + func userChange(event: Event) { if let user = event.user, id = user.id { - let preferences = Client.sharedInstance.users[id]?.preferences - Client.sharedInstance.users[id] = user - Client.sharedInstance.users[id]?.preferences = preferences + let preferences = client.users[id]?.preferences + client.users[id] = user + client.users[id]?.preferences = preferences - if let delegate = Client.sharedInstance.slackEventsDelegate { + if let delegate = client.slackEventsDelegate { delegate.userChanged(user) } } } //MARK: - User Presence - static func presenceChange(event: Event) { + func presenceChange(event: Event) { if let user = event.user, id = user.id { - Client.sharedInstance.users[id]?.presence = event.presence + client.users[id]?.presence = event.presence - if let delegate = Client.sharedInstance.slackEventsDelegate { + if let delegate = client.slackEventsDelegate { delegate.presenceChanged(user, presence: event.presence) } } } //MARK: - Team - static func teamJoin(event: Event) { + func teamJoin(event: Event) { if let user = event.user, id = user.id { - Client.sharedInstance.users[id] = user + client.users[id] = user - if let delegate = Client.sharedInstance.teamEventsDelegate { + if let delegate = client.teamEventsDelegate { delegate.teamJoined(user) } } } - static func teamPlanChange(event: Event) { + func teamPlanChange(event: Event) { if let plan = event.plan { - Client.sharedInstance.team?.plan = plan + client.team?.plan = plan - if let delegate = Client.sharedInstance.teamEventsDelegate { + if let delegate = client.teamEventsDelegate { delegate.teamPlanChanged(plan) } } } - static func teamPreferenceChange(event: Event) { + func teamPreferenceChange(event: Event) { if let name = event.name { - Client.sharedInstance.team?.prefs?[name] = event.value + client.team?.prefs?[name] = event.value - if let delegate = Client.sharedInstance.teamEventsDelegate, value = event.value { + if let delegate = client.teamEventsDelegate, value = event.value { delegate.teamPreferencesChanged(name, value: value) } } } - static func teamNameChange(event: Event) { + func teamNameChange(event: Event) { if let name = event.name { - Client.sharedInstance.team?.name = name + client.team?.name = name - if let delegate = Client.sharedInstance.teamEventsDelegate { + if let delegate = client.teamEventsDelegate { delegate.teamNameChanged(name) } } } - static func teamDomainChange(event: Event) { + func teamDomainChange(event: Event) { if let domain = event.domain { - Client.sharedInstance.team?.domain = domain + client.team?.domain = domain - if let delegate = Client.sharedInstance.teamEventsDelegate { + if let delegate = client.teamEventsDelegate { delegate.teamDomainChanged(domain) } } } - static func emailDomainChange(event: Event) { + func emailDomainChange(event: Event) { if let domain = event.emailDomain { - Client.sharedInstance.team?.emailDomain = domain + client.team?.emailDomain = domain - if let delegate = Client.sharedInstance.teamEventsDelegate { + if let delegate = client.teamEventsDelegate { delegate.teamEmailDomainChanged(domain) } } } - static func emojiChanged(event: Event) { + func emojiChanged(event: Event) { //TODO: Call emoji.list here - if let delegate = Client.sharedInstance.teamEventsDelegate { + if let delegate = client.teamEventsDelegate { delegate.teamEmojiChanged() } } //MARK: - Bots - static func bot(event: Event) { + func bot(event: Event) { if let bot = event.bot, id = bot.id { - Client.sharedInstance.bots[id] = bot + client.bots[id] = bot - if let delegate = Client.sharedInstance.slackEventsDelegate { + if let delegate = client.slackEventsDelegate { delegate.botEvent(bot) } } } //MARK: - Subteams - static func subteam(event: Event) { + func subteam(event: Event) { if let subteam = event.subteam, id = subteam.id { - Client.sharedInstance.userGroups[id] = subteam + client.userGroups[id] = subteam - if let delegate = Client.sharedInstance.subteamEventsDelegate { + if let delegate = client.subteamEventsDelegate { delegate.subteamEvent(subteam) } } } - static func subteamAddedSelf(event: Event) { - if let subteamID = event.subteamID, _ = Client.sharedInstance.authenticatedUser?.userGroups { - Client.sharedInstance.authenticatedUser?.userGroups![subteamID] = subteamID + func subteamAddedSelf(event: Event) { + if let subteamID = event.subteamID, _ = client.authenticatedUser?.userGroups { + client.authenticatedUser?.userGroups![subteamID] = subteamID - if let delegate = Client.sharedInstance.subteamEventsDelegate { + if let delegate = client.subteamEventsDelegate { delegate.subteamSelfAdded(subteamID) } } } - static func subteamRemovedSelf(event: Event) { + func subteamRemovedSelf(event: Event) { if let subteamID = event.subteamID { - Client.sharedInstance.authenticatedUser?.userGroups?.removeValueForKey(subteamID) + client.authenticatedUser?.userGroups?.removeValueForKey(subteamID) - if let delegate = Client.sharedInstance.subteamEventsDelegate { + if let delegate = client.subteamEventsDelegate { delegate.subteamSelfRemoved(subteamID) } } } //MARK: - Authenticated User - static func manualPresenceChange(event: Event) { - Client.sharedInstance.authenticatedUser?.presence = event.presence + func manualPresenceChange(event: Event) { + client.authenticatedUser?.presence = event.presence - if let delegate = Client.sharedInstance.slackEventsDelegate { - delegate.manualPresenceChanged(Client.sharedInstance.authenticatedUser, presence: event.presence) + if let delegate = client.slackEventsDelegate { + delegate.manualPresenceChanged(client.authenticatedUser, presence: event.presence) } } diff --git a/SlackKit/Sources/NetworkInterface.swift b/SlackKit/Sources/NetworkInterface.swift index 92fa3c7..c4b546d 100644 --- a/SlackKit/Sources/NetworkInterface.swift +++ b/SlackKit/Sources/NetworkInterface.swift @@ -1,19 +1,33 @@ // // NetworkInterface.swift -// SlackKit // -// Created by Peter Zignego on 1/18/16. -// Copyright © 2016 Launch Software LLC. All rights reserved. +// Copyright © 2016 Peter Zignego. All rights reserved. // +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. import Foundation -public struct NetworkInterface { +internal struct NetworkInterface { private let apiUrl = "https://slack.com/api/" - internal func request(endpoint: SlackAPIEndpoint, parameters: [String: AnyObject]?, successClosure: ([String: AnyObject])->Void, errorClosure: (SlackError)->Void) { - let token = Client.sharedInstance.token + internal func request(endpoint: SlackAPIEndpoint, token: String, parameters: [String: AnyObject]?, successClosure: ([String: AnyObject])->Void, errorClosure: (SlackError)->Void) { var requestString = "\(apiUrl)\(endpoint.rawValue)?token=\(token)" if let params = parameters { requestString = requestString + requestStringFromParameters(params) @@ -55,4 +69,5 @@ public struct NetworkInterface { return requestString } + } diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index c9bf057..e7e91b4 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -1,15 +1,30 @@ // -// SlackWebAPI.swift -// SlackKit +// SlackWebAPI.swift // -// Created by Peter Zignego on 1/18/16. -// Copyright © 2016 Launch Software LLC. All rights reserved. +// Copyright © 2016 Peter Zignego. All rights reserved. // +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. import Foundation import Starscream -enum SlackAPIEndpoint: String { +internal enum SlackAPIEndpoint: String { case APITest = "api.test" case AuthTest = "auth.test" case ChannelsHistory = "channels.history" @@ -59,39 +74,503 @@ enum SlackAPIEndpoint: String { case UsersSetPresence = "users.setPresence" } -public struct SlackWebAPI { +public class SlackWebAPI { + + public enum InfoType: String { + case Purpose = "purpose" + case Topic = "topic" + } + + public enum ParseMode: String { + case Full = "full" + case None = "none" + } + + public enum Presence: String { + case Auto = "auto" + case Away = "away" + } + + private let client: Client + + required public init(client: Client) { + self.client = client + } + //MARK: - Connection - public static func connect() { - Client.sharedInstance.api.request(SlackAPIEndpoint.RTMStart, parameters: nil) { - (response) -> Void in - Client.sharedInstance.initialSetup(response) - if let socketURL = response["url"] as? String { - let url = NSURL(string: socketURL) - Client.sharedInstance.webSocket = WebSocket(url: url!) - Client.sharedInstance.webSocket?.delegate = Client.sharedInstance - Client.sharedInstance.webSocket?.connect() + public func connect(success success: (connecting: Bool)->Void, failure: (error: SlackError)->Void) { + client.api.request(.RTMStart, token: client.token, parameters: nil, successClosure: { + (response) -> Void in + self.client.initialSetup(response) + if let socketURL = response["url"] as? String { + let url = NSURL(string: socketURL) + self.client.webSocket = WebSocket(url: url!) + self.client.webSocket?.delegate = self.client + self.client.webSocket?.connect() + } + }){ + (error) -> Void in + failure(error: error) } + } + + //MARK: - Auth Test + public func authenticationTest(completion: (authenticated: Bool)->Void) { + client.api.request(.AuthTest, token: client.token, parameters: nil, successClosure: { + (response) -> Void in + completion(authenticated: true) + }) { (error) -> Void in + completion(authenticated: false) + } + } + + //MARK: - Channels + public func getHistoryForChannel(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { + history(.ChannelsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads) { + (history) -> Void in + completion(history: history) + } + } + + public func getChannelInfo(id: String, completion: (channel: Channel?)->Void) { + info(.ChannelsInfo, id: id) {(channel) -> Void in + completion(channel: channel) + } + } + + public func getChannelsList(excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { + list(.ChannelsList) {(channels) -> Void in + completion(channels: channels) + } + } + + public func markChannel(channel: String, timestamp: String, completion: (marked: Bool)->Void) { + mark(.ChannelsMark, channel: channel, timestamp: timestamp){ (marked) -> Void in + completion(marked: marked) + } + } + + public func setChannelPurpose(channel: String, purpose: String, completion: (success: Bool)->Void) { + setInfo(.ChannelsSetPurpose, type: .Purpose, channel: channel, text: purpose) {(success) -> Void in + completion(success: success) + } + } + + public func setChannelTopic(channel: String, topic: String, completion: (success: Bool)->Void) { + setInfo(.ChannelsSetTopic, type: .Topic, channel: channel, text: topic) {(success) -> Void in + completion(success: success) + } + } + + //MARK: - Messaging + public func deleteMessage(channel: String, ts: String, completion: (deleted: Bool)->Void) { + let parameters: [String: AnyObject] = ["channel": channel, "ts": ts] + client.api.request(.ChatDelete, token: client.token, parameters: parameters, successClosure: { (response) -> Void in + completion(deleted: true) + }) {(error) -> Void in + completion(deleted: true) + } + } + + public func sendMessage() { + //TODO: Send message + } + + public func updateMessage(channel: String, ts: String, message: String, attachments: [[String: AnyObject]]? = nil, parse:ParseMode = .None, linkNames: Bool = false, completion: (updated: Bool)->Void) { + var parameters: [String: AnyObject] = ["channel": channel, "ts": ts, "text": message, "parse": parse.rawValue, "link_names": linkNames] + if let attachments = attachments { + parameters["attachments"] = attachments + } + client.api.request(.ChatUpdate, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(updated: true) + }) {(error) -> Void in + completion(updated: false) + } + } + + //MARK: - Emoji + public func emojiList(completion: (emojiList: [String: AnyObject]?)->Void) { + client.api.request(.EmojiList, token: client.token, parameters: nil, successClosure: { + (response) -> Void in + completion(emojiList: response["emoji"] as? [String: AnyObject]) + }) { (error) -> Void in + completion(emojiList: nil) + } + } + + //MARK: - Files + public func deleteFile(fileID: String, completion: (deleted: Bool)->Void) { + client.api.request(.FilesDelete, token: client.token, parameters: ["file":fileID], successClosure: { + (response) -> Void in + completion(deleted: true) + }) {(error) -> Void in + completion(deleted: false) + } + } + + public func uploadFile() { + //TODO: Upload file + } + + //MARK: - Groups + public func closeGroup(groupID: String, completion: (closed: Bool)->Void) { + close(.GroupsClose, channelID: groupID) {(closed) -> Void in + completion(closed: closed) + } + } + + public func getHistoryForGroup(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { + history(.GroupsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads) {(history) -> Void in + completion(history: history) + } + } + + public func getGroupInfo(id: String, completion: (channel: Channel?)->Void) { + info(.GroupsInfo, id: id) {(channel) -> Void in + completion(channel: channel) + } + } + + public func getGroupsList(excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { + list(.GroupsList) {(channels) -> Void in + completion(channels: channels) + } + } + + public func markGroup(channel: String, timestamp: String, completion: (marked: Bool)->Void) { + mark(.GroupsMark, channel: channel, timestamp: timestamp){ (marked) -> Void in + completion(marked: marked) + } + } + + public func openGroup(channel: String, completion: (opened: Bool)->Void) { + client.api.request(.GroupsOpen, token: client.token, parameters: ["channel":channel], successClosure: { + (response) -> Void in + completion(opened: true) + }) {(error) -> Void in + completion(opened: false) + } + } + + public func setGroupPurpose(channel: String, purpose: String, completion: (success: Bool)->Void) { + setInfo(.GroupsSetPurpose, type: .Purpose, channel: channel, text: purpose) {(success) -> Void in + completion(success: success) + } + } + + public func setGroupTopic(channel: String, topic: String, completion: (success: Bool)->Void) { + setInfo(.GroupsSetTopic, type: .Topic, channel: channel, text: topic) {(success) -> Void in + completion(success: success) } } //MARK: - IM - public static func imOpen(userID: String, completion: (imID: String?) -> Void) { - Client.sharedInstance.api.request(SlackAPIEndpoint.IMOpen, parameters: ["user":userID]) { + public func closeIM(channel: String, completion: (closed: Bool)->Void) { + close(.IMClose, channelID: channel) {(closed) -> Void in + completion(closed: closed) + } + } + + public func getHistoryForIM(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { + history(.IMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads) {(history) -> Void in + completion(history: history) + } + } + + public func getIMList(excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { + list(.IMList) {(channels) -> Void in + completion(channels: channels) + } + } + + public func markIM(channel: String, timestamp: String, completion: (marked: Bool)->Void) { + mark(.IMMark, channel: channel, timestamp: timestamp){ (marked) -> Void in + completion(marked: marked) + } + } + + public func openIM(userID: String, completion: (imID: String?)->Void) { + client.api.request(.IMOpen, token: client.token, parameters: ["user":userID], successClosure: { (response) -> Void in if let channel = response["channel"] as? [String: AnyObject], id = channel["id"] as? String { - let exists = Client.sharedInstance.channels.filter{$0.0 == id}.count > 0 + let exists = self.client.channels.filter{$0.0 == id}.count > 0 if exists == true { - Client.sharedInstance.channels[id]?.isOpen = true + self.client.channels[id]?.isOpen = true } else { - Client.sharedInstance.channels[id] = Channel(channel: channel) + self.client.channels[id] = Channel(channel: channel) } completion(imID: id) - if let delegate = Client.sharedInstance.groupEventsDelegate { - delegate.groupOpened(Client.sharedInstance.channels[id]!) + if let delegate = self.client.groupEventsDelegate { + delegate.groupOpened(self.client.channels[id]!) + } + } + + }) {(error) -> Void in + } + } + + //MARK: - MPIM + public func closeMPIM(channel: String, completion: (closed: Bool)->Void) { + close(.MPIMClose, channelID: channel) {(closed) -> Void in + completion(closed: closed) + } + } + + public func getHistoryForMPIM(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { + history(.MPIMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads) {(history) -> Void in + completion(history: history) + } + } + + public func getMPIMList(excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { + list(.MPIMList) {(channels) -> Void in + completion(channels: channels) + } + } + + public func markMPIM(channel: String, timestamp: String, completion: (marked: Bool)->Void) { + mark(.MPIMMark, channel: channel, timestamp: timestamp){ (marked) -> Void in + completion(marked: marked) + } + } + + public func openMPIM(userIDs: [String], completion: (imID: String?)->Void) { + let users = userIDs.joinWithSeparator(",") + client.api.request(.MPIMOpen, token: client.token, parameters: ["users":users], successClosure: { + (response) -> Void in + if let channel = response["group"] as? [String: AnyObject], id = channel["id"] as? String { + let exists = self.client.channels.filter{$0.0 == id}.count > 0 + if exists == true { + self.client.channels[id]?.isOpen = true + } else { + self.client.channels[id] = Channel(channel: channel) } + completion(imID: id) + } + }) {(error) -> Void in + completion(imID: nil) + } + } + + //MARK: - Pins + public func pinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, completion: (pinned: Bool)->Void) { + var parameters: [String: AnyObject] = ["channel":channel] + let optionalParameters = ["file":file, "file_comment":fileComment, "timestamp":timestamp] + for key in optionalParameters.keys { + if optionalParameters[key] != nil { + parameters[key] = optionalParameters[key]! + } + } + client.api.request(.PinsAdd, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(pinned: true) + }){(error) -> Void in + completion(pinned: false) + } + } + + public func unpinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, completion: (unpinned: Bool)->Void) { + var parameters: [String: AnyObject] = ["channel":channel] + let optionalParameters = ["file":file, "file_comment":fileComment, "timestamp":timestamp] + for key in optionalParameters.keys { + if optionalParameters[key] != nil { + parameters[key] = optionalParameters[key]! + } + } + client.api.request(.PinsRemove, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(unpinned: true) + }){(error) -> Void in + completion(unpinned: false) + } + } + + //MARK: - Reactions + // One of file, file_comment, or the combination of channel and timestamp must be specified. + public func addReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, completion: (reacted: Bool)->Void) { + var parameters: [String: AnyObject] = ["name":name] + let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] + for key in optionalParameters.keys { + if optionalParameters[key] != nil { + parameters[key] = optionalParameters[key]! } } + client.api.request(.ReactionsAdd, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(reacted: true) + }) {(error) -> Void in + completion(reacted: false) + } + } + + // One of file, file_comment, or the combination of channel and timestamp must be specified. + public func removeReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, completion: (unreacted: Bool)->Void) { + var parameters: [String: AnyObject] = ["name":name] + let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] + for key in optionalParameters.keys { + if optionalParameters[key] != nil { + parameters[key] = optionalParameters[key]! + } + } + client.api.request(.ReactionsRemove, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(unreacted: true) + }) {(error) -> Void in + completion(unreacted: false) + } + } + + //MARK: - Stars + // One of file, file_comment, channel, or the combination of channel and timestamp must be specified. + public func addStar(file: String, fileComment: String, channel: String, timestamp: String, completion: (starred: Bool)->Void) { + var parameters = [String: AnyObject]() + let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] + for key in optionalParameters.keys { + if optionalParameters[key] != nil { + parameters[key] = optionalParameters[key]! + } + } + client.api.request(.StarsAdd, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(starred: true) + }) {(error) -> Void in + completion(starred: false) + } + } + + // One of file, file_comment, channel, or the combination of channel and timestamp must be specified. + public func removeStar(file: String, fileComment: String, channel: String, timestamp: String, completion: (unstarred: Bool)->Void) { + var parameters = [String: AnyObject]() + let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] + for key in optionalParameters.keys { + if optionalParameters[key] != nil { + parameters[key] = optionalParameters[key]! + } + } + client.api.request(.StarsRemove, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(unstarred: true) + }) {(error) -> Void in + completion(unstarred: false) + } } + + //MARK: - Team + public func getTeamInfo(completion: (info: [String: AnyObject]?)->Void) { + client.api.request(.TeamInfo, token: client.token, parameters: nil, successClosure: { + (response) -> Void in + completion(info: response["team"] as? [String: AnyObject]) + }) {(error) -> Void in + completion(info: nil) + } + } + + //MARK: - Users + public func getUserPresence(user: String, completion: (presence: String?)->Void) { + client.api.request(.UsersGetPresence, token: client.token, parameters: ["user":user], successClosure: { + (response) -> Void in + completion(presence: response["presence"] as? String) + }){(error) -> Void in + completion(presence: nil) + } + } + + public func getUserInfo(id: String, completion: (user: User?)->Void) { + client.api.request(.UsersInfo, token: client.token, parameters: ["user":id], successClosure: { + (response) -> Void in + completion(user: User(user: response["user"] as? [String: AnyObject])) + }) {(error) -> Void in + completion(user: nil) + } + } + + public func getUserList(includePresence: Bool = false, completion: (userList: [[String: AnyObject]]?)->Void) { + client.api.request(.UsersList, token: client.token, parameters: ["presence":includePresence], successClosure: { + (response) -> Void in + completion(userList: response["members"] as? [[String: AnyObject]]) + }){(error) -> Void in + completion(userList: nil) + } + } + + public func setUserActive(completion: (success: Bool)->Void) { + client.api.request(.UsersSetActive, token: client.token, parameters: nil, successClosure: { + (response) -> Void in + completion(success: true) + }) {(error) -> Void in + completion(success: false) + } + } + + public func setUserPresence(presence: Presence, completion: (success: Bool)->Void) { + client.api.request(.UsersSetPresence, token: client.token, parameters: ["presence":presence.rawValue], successClosure: { + (response) -> Void in + completion(success:true) + }) {(error) -> Void in + completion(success:false) + } + } + + //MARK: - Channel Utilities + private func close(endpoint: SlackAPIEndpoint, channelID: String, completion: (closed: Bool)->Void) { + client.api.request(endpoint, token: client.token, parameters: ["channel":channelID], successClosure: { + (response) -> Void in + completion(closed: true) + }) {(error) -> Void in + completion(closed: false) + } + } + + private func history(endpoint: SlackAPIEndpoint, id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { + let parameters: [String: AnyObject] = ["channel": id, "latest": latest, "oldest": oldest, "inclusive":inclusive, "count":count, "unreads":unreads] + client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(history: response) + }) {(error) -> Void in + completion(history: nil) + } + } + + private func info(endpoint: SlackAPIEndpoint, id: String, completion: (channel: Channel?)->Void) { + client.api.request(endpoint, token: client.token, parameters: ["channel": id], successClosure: { + (response) -> Void in + completion(channel: Channel(channel: response["channel"] as? [String: AnyObject])) + }) {(error) -> Void in + completion(channel: nil) + } + } + + private func list(endpoint: SlackAPIEndpoint, excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { + let parameters: [String: AnyObject] = ["exclude_archived": excludeArchived] + client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(channels: response["channels"] as? [[String: AnyObject]]) + }) {(error) -> Void in + completion(channels: nil) + } + } + + private func mark(endpoint: SlackAPIEndpoint, channel: String, timestamp: String, completion: (marked: Bool)->Void) { + let parameters: [String: AnyObject] = ["channel": channel, "ts": timestamp] + client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(marked: true) + }) {(error) -> Void in + completion(marked: false) + } + } + + private func setInfo(endpoint: SlackAPIEndpoint, type: InfoType, channel: String, text: String, completion: (success: Bool)->Void) { + let parameters: [String: AnyObject] = ["channel": channel, type.rawValue: text] + client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + completion(success: true) + }) {(error) -> Void in + completion(success: false) + } + } + } From f2f25763e7c73380bcf944a9abccd37c08d85c74 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 10:16:07 -0500 Subject: [PATCH 08/26] Move source file to Sources --- SlackKit/{ => Sources}/SlackWebAPIErrorDispatcher.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename SlackKit/{ => Sources}/SlackWebAPIErrorDispatcher.swift (100%) diff --git a/SlackKit/SlackWebAPIErrorDispatcher.swift b/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift similarity index 100% rename from SlackKit/SlackWebAPIErrorDispatcher.swift rename to SlackKit/Sources/SlackWebAPIErrorDispatcher.swift From 1a787019ecf3256fa54194f57117a9b184b4d258 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 11:16:07 -0500 Subject: [PATCH 09/26] Add error handling --- SlackKit/Sources/ClientExtensions.swift | 6 +- SlackKit/Sources/SlackWebAPI.swift | 358 ++++++++++++++---------- 2 files changed, 210 insertions(+), 154 deletions(-) diff --git a/SlackKit/Sources/ClientExtensions.swift b/SlackKit/Sources/ClientExtensions.swift index bed1e9f..6b3a092 100644 --- a/SlackKit/Sources/ClientExtensions.swift +++ b/SlackKit/Sources/ClientExtensions.swift @@ -43,13 +43,13 @@ extension Client { return users.filter{$0.1.name == stripString(name)}.first?.0 } - public func getImIDForUserWithID(id: String, completion: (imID: String?) -> Void) { + public func getImIDForUserWithID(id: String, success: (imID: String?)->Void, failure: (error: SlackError)->Void) { let ims = channels.filter{$0.1.isIM == true} let channel = ims.filter{$0.1.user == id}.first if let channel = channel { - completion(imID: channel.0) + success(imID: channel.0) } else { - slackWebAPI?.openIM(id, completion: completion) + slackWebAPI?.openIM(id, success: success, failure: failure) } } diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index e7e91b4..827a1e0 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -108,68 +108,85 @@ public class SlackWebAPI { self.client.webSocket = WebSocket(url: url!) self.client.webSocket?.delegate = self.client self.client.webSocket?.connect() + success(connecting: true) } - }){ - (error) -> Void in + }) {(error) -> Void in failure(error: error) } } //MARK: - Auth Test - public func authenticationTest(completion: (authenticated: Bool)->Void) { + public func authenticationTest(success: (authenticated: Bool)->Void, failure: (error: SlackError)->Void) { client.api.request(.AuthTest, token: client.token, parameters: nil, successClosure: { (response) -> Void in - completion(authenticated: true) - }) { (error) -> Void in - completion(authenticated: false) + success(authenticated: true) + }) {(error) -> Void in + failure(error: error) } } //MARK: - Channels - public func getHistoryForChannel(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { - history(.ChannelsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads) { + public func getHistoryForChannel(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + history(.ChannelsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in - completion(history: history) + success(history:history) + }) {(error) -> Void in + failure(error: error) } } - public func getChannelInfo(id: String, completion: (channel: Channel?)->Void) { - info(.ChannelsInfo, id: id) {(channel) -> Void in - completion(channel: channel) + public func getChannelInfo(id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { + info(.ChannelsInfo, id: id, success: { + (channel) -> Void in + success(channel: channel) + }) { (error) -> Void in + failure(error: error) } } - public func getChannelsList(excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { - list(.ChannelsList) {(channels) -> Void in - completion(channels: channels) + public func getChannelsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + list(.ChannelsList, excludeArchived: excludeArchived, success: { + (channels) -> Void in + success(channels: channels) + }) {(error) -> Void in + failure(error: error) } } - public func markChannel(channel: String, timestamp: String, completion: (marked: Bool)->Void) { - mark(.ChannelsMark, channel: channel, timestamp: timestamp){ (marked) -> Void in - completion(marked: marked) + public func markChannel(channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { + mark(.ChannelsMark, channel: channel, timestamp: timestamp, success: { + (marked) -> Void in + success(marked:marked) + }) {(error) -> Void in + failure(error: error) } } - public func setChannelPurpose(channel: String, purpose: String, completion: (success: Bool)->Void) { - setInfo(.ChannelsSetPurpose, type: .Purpose, channel: channel, text: purpose) {(success) -> Void in - completion(success: success) + public func setChannelPurpose(channel: String, purpose: String, success: (purposeSet: Bool)->Void, failure: (error: SlackError)->Void) { + setInfo(.ChannelsSetPurpose, type: .Purpose, channel: channel, text: purpose, success: { + (purposeSet) -> Void in + success(purposeSet: purposeSet) + }) { (error) -> Void in + failure(error: error) } } - public func setChannelTopic(channel: String, topic: String, completion: (success: Bool)->Void) { - setInfo(.ChannelsSetTopic, type: .Topic, channel: channel, text: topic) {(success) -> Void in - completion(success: success) + public func setChannelTopic(channel: String, topic: String, success: (topicSet: Bool)->Void, failure: (error: SlackError)->Void) { + setInfo(.ChannelsSetTopic, type: .Topic, channel: channel, text: topic, success: { + (topicSet) -> Void in + success(topicSet: topicSet) + }) {(error) -> Void in + failure(error: error) } } //MARK: - Messaging - public func deleteMessage(channel: String, ts: String, completion: (deleted: Bool)->Void) { + public func deleteMessage(channel: String, ts: String, success: (deleted: Bool)->Void, failure: (error: SlackError)->Void) { let parameters: [String: AnyObject] = ["channel": channel, "ts": ts] client.api.request(.ChatDelete, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(deleted: true) + success(deleted: true) }) {(error) -> Void in - completion(deleted: true) + failure(error: error) } } @@ -177,36 +194,36 @@ public class SlackWebAPI { //TODO: Send message } - public func updateMessage(channel: String, ts: String, message: String, attachments: [[String: AnyObject]]? = nil, parse:ParseMode = .None, linkNames: Bool = false, completion: (updated: Bool)->Void) { + public func updateMessage(channel: String, ts: String, message: String, attachments: [[String: AnyObject]]? = nil, parse:ParseMode = .None, linkNames: Bool = false, success: (updated: Bool)->Void, failure: (error: SlackError)->Void) { var parameters: [String: AnyObject] = ["channel": channel, "ts": ts, "text": message, "parse": parse.rawValue, "link_names": linkNames] if let attachments = attachments { parameters["attachments"] = attachments } client.api.request(.ChatUpdate, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(updated: true) + success(updated: true) }) {(error) -> Void in - completion(updated: false) + failure(error: error) } } //MARK: - Emoji - public func emojiList(completion: (emojiList: [String: AnyObject]?)->Void) { + public func emojiList(success: (emojiList: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { client.api.request(.EmojiList, token: client.token, parameters: nil, successClosure: { (response) -> Void in - completion(emojiList: response["emoji"] as? [String: AnyObject]) + success(emojiList: response["emoji"] as? [String: AnyObject]) }) { (error) -> Void in - completion(emojiList: nil) + failure(error: error) } } //MARK: - Files - public func deleteFile(fileID: String, completion: (deleted: Bool)->Void) { + public func deleteFile(fileID: String, success: (deleted: Bool)->Void, failure: (error: SlackError)->Void) { client.api.request(.FilesDelete, token: client.token, parameters: ["file":fileID], successClosure: { (response) -> Void in - completion(deleted: true) + success(deleted: true) }) {(error) -> Void in - completion(deleted: false) + failure(error: error) } } @@ -215,83 +232,116 @@ public class SlackWebAPI { } //MARK: - Groups - public func closeGroup(groupID: String, completion: (closed: Bool)->Void) { - close(.GroupsClose, channelID: groupID) {(closed) -> Void in - completion(closed: closed) + public func closeGroup(groupID: String, success: (closed: Bool)->Void, failure: (error: SlackError)->Void) { + close(.GroupsClose, channelID: groupID, success: { + (closed) -> Void in + success(closed:closed) + }) {(error) -> Void in + failure(error:error) } } - public func getHistoryForGroup(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { - history(.GroupsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads) {(history) -> Void in - completion(history: history) + public func getHistoryForGroup(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + history(.GroupsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { + (history) -> Void in + success(history: history) + }) {(error) -> Void in + failure(error: error) } } - public func getGroupInfo(id: String, completion: (channel: Channel?)->Void) { - info(.GroupsInfo, id: id) {(channel) -> Void in - completion(channel: channel) + public func getGroupInfo(id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { + info(.GroupsInfo, id: id, success: { + (channel) -> Void in + success(channel: channel) + }) {(error) -> Void in + failure(error: error) } } - public func getGroupsList(excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { - list(.GroupsList) {(channels) -> Void in - completion(channels: channels) + public func getGroupsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + list(.GroupsList, excludeArchived: excludeArchived, success: { + (channels) -> Void in + success(channels: channels) + }) {(error) -> Void in + failure(error: error) } } - public func markGroup(channel: String, timestamp: String, completion: (marked: Bool)->Void) { - mark(.GroupsMark, channel: channel, timestamp: timestamp){ (marked) -> Void in - completion(marked: marked) + public func markGroup(channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { + mark(.GroupsMark, channel: channel, timestamp: timestamp, success: { + (marked) -> Void in + success(marked: marked) + }) {(error) -> Void in + failure(error: error) } } - public func openGroup(channel: String, completion: (opened: Bool)->Void) { + public func openGroup(channel: String, success: (opened: Bool)->Void, failure: (error: SlackError)->Void) { client.api.request(.GroupsOpen, token: client.token, parameters: ["channel":channel], successClosure: { (response) -> Void in - completion(opened: true) + success(opened: true) }) {(error) -> Void in - completion(opened: false) + failure(error: error) } } - public func setGroupPurpose(channel: String, purpose: String, completion: (success: Bool)->Void) { - setInfo(.GroupsSetPurpose, type: .Purpose, channel: channel, text: purpose) {(success) -> Void in - completion(success: success) + public func setGroupPurpose(channel: String, purpose: String, success: (purposeSet: Bool)->Void, failure: (error: SlackError)->Void) { + setInfo(.GroupsSetPurpose, type: .Purpose, channel: channel, text: purpose, success: { + (purposeSet) -> Void in + success(purposeSet: purposeSet) + }) {(error) -> Void in + failure(error: error) } } - public func setGroupTopic(channel: String, topic: String, completion: (success: Bool)->Void) { - setInfo(.GroupsSetTopic, type: .Topic, channel: channel, text: topic) {(success) -> Void in - completion(success: success) + public func setGroupTopic(channel: String, topic: String, success: (topicSet: Bool)->Void, failure: (error: SlackError)->Void) { + setInfo(.GroupsSetTopic, type: .Topic, channel: channel, text: topic, success: { + (topicSet) -> Void in + success(topicSet: topicSet) + }) {(error) -> Void in + failure(error: error) } } //MARK: - IM - public func closeIM(channel: String, completion: (closed: Bool)->Void) { - close(.IMClose, channelID: channel) {(closed) -> Void in - completion(closed: closed) + public func closeIM(channel: String, success: (closed: Bool)->Void, failure: (error: SlackError)->Void) { + close(.IMClose, channelID: channel, success: { + (closed) -> Void in + success(closed: closed) + }) {(error) -> Void in + failure(error: error) } } - public func getHistoryForIM(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { - history(.IMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads) {(history) -> Void in - completion(history: history) + public func getHistoryForIM(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + history(.IMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { + (history) -> Void in + success(history: history) + }) {(error) -> Void in + failure(error: error) } } - public func getIMList(excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { - list(.IMList) {(channels) -> Void in - completion(channels: channels) + public func getIMList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + list(.IMList, excludeArchived: excludeArchived, success: { + (channels) -> Void in + success(channels: channels) + }) {(error) -> Void in + failure(error: error) } } - public func markIM(channel: String, timestamp: String, completion: (marked: Bool)->Void) { - mark(.IMMark, channel: channel, timestamp: timestamp){ (marked) -> Void in - completion(marked: marked) + public func markIM(channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { + mark(.IMMark, channel: channel, timestamp: timestamp, success: { + (marked) -> Void in + success(marked: marked) + }) {(error) -> Void in + failure(error: error) } } - public func openIM(userID: String, completion: (imID: String?)->Void) { + public func openIM(userID: String, success: (imID: String?)->Void, failure: (error: SlackError)->Void) { client.api.request(.IMOpen, token: client.token, parameters: ["user":userID], successClosure: { (response) -> Void in if let channel = response["channel"] as? [String: AnyObject], id = channel["id"] as? String { @@ -301,7 +351,7 @@ public class SlackWebAPI { } else { self.client.channels[id] = Channel(channel: channel) } - completion(imID: id) + success(imID: id) if let delegate = self.client.groupEventsDelegate { delegate.groupOpened(self.client.channels[id]!) @@ -309,54 +359,60 @@ public class SlackWebAPI { } }) {(error) -> Void in + failure(error: error) } } //MARK: - MPIM - public func closeMPIM(channel: String, completion: (closed: Bool)->Void) { - close(.MPIMClose, channelID: channel) {(closed) -> Void in - completion(closed: closed) + public func closeMPIM(channel: String, success: (closed: Bool)->Void, failure: (error: SlackError)->Void) { + close(.MPIMClose, channelID: channel, success: { + (closed) -> Void in + success(closed: closed) + }) {(error) -> Void in + failure(error: error) } } - public func getHistoryForMPIM(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { - history(.MPIMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads) {(history) -> Void in - completion(history: history) + public func getHistoryForMPIM(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + history(.MPIMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { + (history) -> Void in + success(history: history) + }) {(error) -> Void in + failure(error: error) } } - public func getMPIMList(excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { - list(.MPIMList) {(channels) -> Void in - completion(channels: channels) + public func getMPIMList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + list(.MPIMList, excludeArchived: excludeArchived, success: { + (channels) -> Void in + success(channels: channels) + }) {(error) -> Void in + failure(error: error) } } - public func markMPIM(channel: String, timestamp: String, completion: (marked: Bool)->Void) { - mark(.MPIMMark, channel: channel, timestamp: timestamp){ (marked) -> Void in - completion(marked: marked) + public func markMPIM(channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { + mark(.MPIMMark, channel: channel, timestamp: timestamp, success: { + (marked) -> Void in + success(marked: marked) + }) {(error) -> Void in + failure(error: error) } } - public func openMPIM(userIDs: [String], completion: (imID: String?)->Void) { + public func openMPIM(userIDs: [String], success: (imID: String?)->Void, failure: (error: SlackError)->Void) { let users = userIDs.joinWithSeparator(",") client.api.request(.MPIMOpen, token: client.token, parameters: ["users":users], successClosure: { (response) -> Void in - if let channel = response["group"] as? [String: AnyObject], id = channel["id"] as? String { - let exists = self.client.channels.filter{$0.0 == id}.count > 0 - if exists == true { - self.client.channels[id]?.isOpen = true - } else { - self.client.channels[id] = Channel(channel: channel) - } - completion(imID: id) - } + let group = response["group"] as? [String: AnyObject] + success(imID: group?["id"] as? String) }) {(error) -> Void in - completion(imID: nil) + failure(error: error) } } //MARK: - Pins - public func pinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, completion: (pinned: Bool)->Void) { + public func pinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: (pinned: Bool)->Void, failure: (error: SlackError)->Void) { var parameters: [String: AnyObject] = ["channel":channel] let optionalParameters = ["file":file, "file_comment":fileComment, "timestamp":timestamp] for key in optionalParameters.keys { @@ -366,13 +422,13 @@ public class SlackWebAPI { } client.api.request(.PinsAdd, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(pinned: true) + success(pinned: true) }){(error) -> Void in - completion(pinned: false) + failure(error: error) } } - public func unpinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, completion: (unpinned: Bool)->Void) { + public func unpinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: (unpinned: Bool)->Void, failure: (error: SlackError)->Void) { var parameters: [String: AnyObject] = ["channel":channel] let optionalParameters = ["file":file, "file_comment":fileComment, "timestamp":timestamp] for key in optionalParameters.keys { @@ -382,15 +438,15 @@ public class SlackWebAPI { } client.api.request(.PinsRemove, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(unpinned: true) + success(unpinned: true) }){(error) -> Void in - completion(unpinned: false) + failure(error: error) } } //MARK: - Reactions // One of file, file_comment, or the combination of channel and timestamp must be specified. - public func addReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, completion: (reacted: Bool)->Void) { + public func addReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (reacted: Bool)->Void, failure: (error: SlackError)->Void) { var parameters: [String: AnyObject] = ["name":name] let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] for key in optionalParameters.keys { @@ -400,14 +456,14 @@ public class SlackWebAPI { } client.api.request(.ReactionsAdd, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(reacted: true) + success(reacted: true) }) {(error) -> Void in - completion(reacted: false) + failure(error: error) } } // One of file, file_comment, or the combination of channel and timestamp must be specified. - public func removeReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, completion: (unreacted: Bool)->Void) { + public func removeReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (unreacted: Bool)->Void, failure: (error: SlackError)->Void) { var parameters: [String: AnyObject] = ["name":name] let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] for key in optionalParameters.keys { @@ -417,15 +473,15 @@ public class SlackWebAPI { } client.api.request(.ReactionsRemove, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(unreacted: true) + success(unreacted: true) }) {(error) -> Void in - completion(unreacted: false) + failure(error: error) } } //MARK: - Stars // One of file, file_comment, channel, or the combination of channel and timestamp must be specified. - public func addStar(file: String, fileComment: String, channel: String, timestamp: String, completion: (starred: Bool)->Void) { + public func addStar(file: String, fileComment: String, channel: String, timestamp: String, success: (starred: Bool)->Void, failure: (error: SlackError)->Void) { var parameters = [String: AnyObject]() let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] for key in optionalParameters.keys { @@ -435,14 +491,14 @@ public class SlackWebAPI { } client.api.request(.StarsAdd, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(starred: true) + success(starred: true) }) {(error) -> Void in - completion(starred: false) + failure(error: error) } } // One of file, file_comment, channel, or the combination of channel and timestamp must be specified. - public func removeStar(file: String, fileComment: String, channel: String, timestamp: String, completion: (unstarred: Bool)->Void) { + public func removeStar(file: String, fileComment: String, channel: String, timestamp: String, success: (unstarred: Bool)->Void, failure: (error: SlackError)->Void) { var parameters = [String: AnyObject]() let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] for key in optionalParameters.keys { @@ -452,124 +508,124 @@ public class SlackWebAPI { } client.api.request(.StarsRemove, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(unstarred: true) + success(unstarred: true) }) {(error) -> Void in - completion(unstarred: false) + failure(error: error) } } //MARK: - Team - public func getTeamInfo(completion: (info: [String: AnyObject]?)->Void) { + public func getTeamInfo(success: (info: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { client.api.request(.TeamInfo, token: client.token, parameters: nil, successClosure: { (response) -> Void in - completion(info: response["team"] as? [String: AnyObject]) + success(info: response["team"] as? [String: AnyObject]) }) {(error) -> Void in - completion(info: nil) + failure(error: error) } } //MARK: - Users - public func getUserPresence(user: String, completion: (presence: String?)->Void) { + public func getUserPresence(user: String, success: (presence: String?)->Void, failure: (error: SlackError)->Void) { client.api.request(.UsersGetPresence, token: client.token, parameters: ["user":user], successClosure: { (response) -> Void in - completion(presence: response["presence"] as? String) + success(presence: response["presence"] as? String) }){(error) -> Void in - completion(presence: nil) + failure(error: error) } } - public func getUserInfo(id: String, completion: (user: User?)->Void) { + public func getUserInfo(id: String, success: (user: User?)->Void, failure: (error: SlackError)->Void) { client.api.request(.UsersInfo, token: client.token, parameters: ["user":id], successClosure: { (response) -> Void in - completion(user: User(user: response["user"] as? [String: AnyObject])) + success(user: User(user: response["user"] as? [String: AnyObject])) }) {(error) -> Void in - completion(user: nil) + failure(error: error) } } - public func getUserList(includePresence: Bool = false, completion: (userList: [[String: AnyObject]]?)->Void) { + public func getUserList(includePresence: Bool = false, success: (userList: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { client.api.request(.UsersList, token: client.token, parameters: ["presence":includePresence], successClosure: { (response) -> Void in - completion(userList: response["members"] as? [[String: AnyObject]]) + success(userList: response["members"] as? [[String: AnyObject]]) }){(error) -> Void in - completion(userList: nil) + failure(error: error) } } - public func setUserActive(completion: (success: Bool)->Void) { + public func setUserActive(success: (success: Bool)->Void, failure: (error: SlackError)->Void) { client.api.request(.UsersSetActive, token: client.token, parameters: nil, successClosure: { (response) -> Void in - completion(success: true) + success(success: true) }) {(error) -> Void in - completion(success: false) + failure(error: error) } } - public func setUserPresence(presence: Presence, completion: (success: Bool)->Void) { + public func setUserPresence(presence: Presence, success: (success: Bool)->Void, failure: (error: SlackError)->Void) { client.api.request(.UsersSetPresence, token: client.token, parameters: ["presence":presence.rawValue], successClosure: { (response) -> Void in - completion(success:true) + success(success:true) }) {(error) -> Void in - completion(success:false) + failure(error: error) } } //MARK: - Channel Utilities - private func close(endpoint: SlackAPIEndpoint, channelID: String, completion: (closed: Bool)->Void) { + private func close(endpoint: SlackAPIEndpoint, channelID: String, success: (closed: Bool)->Void, failure: (error: SlackError)->Void) { client.api.request(endpoint, token: client.token, parameters: ["channel":channelID], successClosure: { (response) -> Void in - completion(closed: true) + success(closed: true) }) {(error) -> Void in - completion(closed: false) + failure(error: error) } } - private func history(endpoint: SlackAPIEndpoint, id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, completion: (history: [String: AnyObject]?)->Void) { + private func history(endpoint: SlackAPIEndpoint, id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { let parameters: [String: AnyObject] = ["channel": id, "latest": latest, "oldest": oldest, "inclusive":inclusive, "count":count, "unreads":unreads] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(history: response) + success(history: response) }) {(error) -> Void in - completion(history: nil) + failure(error: error) } } - private func info(endpoint: SlackAPIEndpoint, id: String, completion: (channel: Channel?)->Void) { + private func info(endpoint: SlackAPIEndpoint, id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { client.api.request(endpoint, token: client.token, parameters: ["channel": id], successClosure: { (response) -> Void in - completion(channel: Channel(channel: response["channel"] as? [String: AnyObject])) + success(channel: Channel(channel: response["channel"] as? [String: AnyObject])) }) {(error) -> Void in - completion(channel: nil) + failure(error: error) } } - private func list(endpoint: SlackAPIEndpoint, excludeArchived: Bool = false, completion: (channels: [[String: AnyObject]]?)->Void) { + private func list(endpoint: SlackAPIEndpoint, excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { let parameters: [String: AnyObject] = ["exclude_archived": excludeArchived] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(channels: response["channels"] as? [[String: AnyObject]]) + success(channels: response["channels"] as? [[String: AnyObject]]) }) {(error) -> Void in - completion(channels: nil) + failure(error: error) } } - private func mark(endpoint: SlackAPIEndpoint, channel: String, timestamp: String, completion: (marked: Bool)->Void) { + private func mark(endpoint: SlackAPIEndpoint, channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { let parameters: [String: AnyObject] = ["channel": channel, "ts": timestamp] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(marked: true) + success(marked: true) }) {(error) -> Void in - completion(marked: false) + failure(error: error) } } - private func setInfo(endpoint: SlackAPIEndpoint, type: InfoType, channel: String, text: String, completion: (success: Bool)->Void) { + private func setInfo(endpoint: SlackAPIEndpoint, type: InfoType, channel: String, text: String, success: (success: Bool)->Void, failure: (error: SlackError)->Void) { let parameters: [String: AnyObject] = ["channel": channel, type.rawValue: text] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - completion(success: true) + success(success: true) }) {(error) -> Void in - completion(success: false) + failure(error: error) } } From 78d31af3f7a870a8b88e94edc68d93752077f0a7 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 12:04:09 -0500 Subject: [PATCH 10/26] Move client code out of SlackWebAPI --- SlackKit/Sources/Client.swift | 13 +++++++++---- SlackKit/Sources/SlackWebAPI.swift | 12 ++---------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index 33ee4e6..3440dc0 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -70,10 +70,15 @@ public class Client: WebSocketDelegate { dispatcher = EventDispatcher(client: self) slackWebAPI = SlackWebAPI(client: self) slackWebAPI?.connect(success: { - (connecting) -> Void in - - }, - failure: { (error) -> Void in + (response) -> Void in + self.initialSetup(response) + if let socketURL = response["url"] as? String { + let url = NSURL(string: socketURL) + self.webSocket = WebSocket(url: url!) + self.webSocket?.delegate = self + self.webSocket?.connect() + } + }, failure: {(error) -> Void in }) } diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index 827a1e0..bb1cae5 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -22,7 +22,6 @@ // THE SOFTWARE. import Foundation -import Starscream internal enum SlackAPIEndpoint: String { case APITest = "api.test" @@ -99,17 +98,10 @@ public class SlackWebAPI { } //MARK: - Connection - public func connect(success success: (connecting: Bool)->Void, failure: (error: SlackError)->Void) { + public func connect(success success: (response: [String: AnyObject])->Void, failure: (error: SlackError)->Void) { client.api.request(.RTMStart, token: client.token, parameters: nil, successClosure: { (response) -> Void in - self.client.initialSetup(response) - if let socketURL = response["url"] as? String { - let url = NSURL(string: socketURL) - self.client.webSocket = WebSocket(url: url!) - self.client.webSocket?.delegate = self.client - self.client.webSocket?.connect() - success(connecting: true) - } + success(response: response) }) {(error) -> Void in failure(error: error) } From 9f266fff6450be6c67cb8c47f49317990ad1ab1d Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 12:26:53 -0500 Subject: [PATCH 11/26] WebAPI clean up --- SlackKit/Sources/SlackWebAPI.swift | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index bb1cae5..2fc35f2 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -336,20 +336,8 @@ public class SlackWebAPI { public func openIM(userID: String, success: (imID: String?)->Void, failure: (error: SlackError)->Void) { client.api.request(.IMOpen, token: client.token, parameters: ["user":userID], successClosure: { (response) -> Void in - if let channel = response["channel"] as? [String: AnyObject], id = channel["id"] as? String { - let exists = self.client.channels.filter{$0.0 == id}.count > 0 - if exists == true { - self.client.channels[id]?.isOpen = true - } else { - self.client.channels[id] = Channel(channel: channel) - } - success(imID: id) - - if let delegate = self.client.groupEventsDelegate { - delegate.groupOpened(self.client.channels[id]!) - } - } - + let group = response["channel"] as? [String: AnyObject] + success(imID: group?["id"] as? String) }) {(error) -> Void in failure(error: error) } From a74aba3c5e5411a6c082fb94191aed6bf32be071 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 14:14:06 -0500 Subject: [PATCH 12/26] Remove depreciated parameters --- SlackKit/Sources/File.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/SlackKit/Sources/File.swift b/SlackKit/Sources/File.swift index 221e665..eb0e482 100644 --- a/SlackKit/Sources/File.swift +++ b/SlackKit/Sources/File.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. public struct File { - + public let id: String? public let created: Int? public let name: String? @@ -36,8 +36,6 @@ public struct File { public let isExternal: Bool? public let externalType: String? public let size: Int? - public let url: String? - public let urlDownload: String? public let urlPrivate: String? public let urlPrivateDownload: String? public let thumb64: String? @@ -78,8 +76,6 @@ public struct File { isExternal = file?["is_external"] as? Bool externalType = file?["external_type"] as? String size = file?["size"] as? Int - url = file?["url"] as? String - urlDownload = file?["url_download"] as? String urlPrivate = file?["url_private"] as? String urlPrivateDownload = file?["url_private_download"] as? String thumb64 = file?["thumb_64"] as? String @@ -122,8 +118,6 @@ public struct File { isExternal = nil externalType = nil size = nil - url = nil - urlDownload = nil urlPrivate = nil urlPrivateDownload = nil thumb64 = nil From ab41c148cd2f5d357e09cd9c1a6f04fab4704962 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 14:14:37 -0500 Subject: [PATCH 13/26] Make Slack message formatting a string extension --- SlackKit/Sources/Client.swift | 9 +-------- SlackKit/Sources/ClientExtensions.swift | 10 ++++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index 3440dc0..4efa23e 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -97,7 +97,7 @@ public class Client: WebSocketDelegate { "id": NSDate().timeIntervalSince1970, "type": "message", "channel": message.channel, - "text": slackFormatEscaping(message.msg) + "text": message.msg.slackFormatEscaping() ] addSentMessage(json) do { @@ -118,13 +118,6 @@ public class Client: WebSocketDelegate { sentMessages[ts!.stringValue] = Message(message: message) } - private func slackFormatEscaping(string: String) -> String { - var escapedString = string.stringByReplacingOccurrencesOfString("&", withString: "&") - escapedString = escapedString.stringByReplacingOccurrencesOfString("<", withString: "<") - escapedString = escapedString.stringByReplacingOccurrencesOfString(">", withString: ">") - return escapedString - } - //MARK: - Client setup internal func initialSetup(json: [String: AnyObject]) { team = Team(team: json["team"] as? [String: AnyObject]) diff --git a/SlackKit/Sources/ClientExtensions.swift b/SlackKit/Sources/ClientExtensions.swift index 6b3a092..1a62f1e 100644 --- a/SlackKit/Sources/ClientExtensions.swift +++ b/SlackKit/Sources/ClientExtensions.swift @@ -64,3 +64,13 @@ extension Client { } } + +extension String { + + func slackFormatEscaping() -> String { + var escapedString = stringByReplacingOccurrencesOfString("&", withString: "&") + escapedString = stringByReplacingOccurrencesOfString("<", withString: "<") + escapedString = stringByReplacingOccurrencesOfString(">", withString: ">") + return escapedString + } +} \ No newline at end of file From e4f0429ffbbd57d41fd082ab379674e75a52569c Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 14:15:04 -0500 Subject: [PATCH 14/26] Send message implementation --- SlackKit/Sources/SlackWebAPI.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index 2fc35f2..ba5a311 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -182,8 +182,20 @@ public class SlackWebAPI { } } - public func sendMessage() { - //TODO: Send message + public func sendMessage(channel: String, text: String, username: String? = nil, asUser: Bool = false, parse: ParseMode = .Full, linkNames: Bool = false, attachments: [[String: AnyObject]]? = nil, unfurlLinks: Bool = false, unfurlMedia: Bool = false, iconURL: String? = nil, iconEmoji: String? = nil, success: ((ts: String?, channel: String?))->Void, failure: (error: SlackError)->Void) { + var parameters: [String: AnyObject] = ["channel":channel, "text":text.slackFormatEscaping(), "as_user":asUser, "parse":parse.rawValue, "link_names":linkNames, "unfurl_links":unfurlLinks, "unfurlMedia":unfurlMedia] + let optionalParameters: [String: AnyObject?] = ["username":username, "attachments":attachments, "icon_url":iconURL, "icon_emoji":iconEmoji] + for key in optionalParameters.keys { + if optionalParameters[key] != nil { + parameters[key] = optionalParameters[key]! + } + } + client.api.request(.ChatPostMessage, token: client.token, parameters: parameters, successClosure: { + (response) -> Void in + success((ts: response["ts"] as? String, response["channel"] as? String)) + }) {(error) -> Void in + failure(error: error) + } } public func updateMessage(channel: String, ts: String, message: String, attachments: [[String: AnyObject]]? = nil, parse:ParseMode = .None, linkNames: Bool = false, success: (updated: Bool)->Void, failure: (error: SlackError)->Void) { From 39fa80904f6176ee34cb8d9faa91972a77ddbcc5 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 16:35:19 -0500 Subject: [PATCH 15/26] WebAPI interface clean up --- RLS/Assets.xcassets/Contents.json | 6 + SlackKit/Sources/SlackWebAPI.swift | 196 +++++++++++++++-------------- 2 files changed, 107 insertions(+), 95 deletions(-) create mode 100644 RLS/Assets.xcassets/Contents.json diff --git a/RLS/Assets.xcassets/Contents.json b/RLS/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/RLS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index ba5a311..e60b282 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -97,8 +97,8 @@ public class SlackWebAPI { self.client = client } - //MARK: - Connection - public func connect(success success: (response: [String: AnyObject])->Void, failure: (error: SlackError)->Void) { + //MARK: - RTM + public func rtmStart(success success: (response: [String: AnyObject])->Void, failure: (error: SlackError)->Void) { client.api.request(.RTMStart, token: client.token, parameters: nil, successClosure: { (response) -> Void in success(response: response) @@ -118,7 +118,7 @@ public class SlackWebAPI { } //MARK: - Channels - public func getHistoryForChannel(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func channelHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { history(.ChannelsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in success(history:history) @@ -127,7 +127,7 @@ public class SlackWebAPI { } } - public func getChannelInfo(id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { + public func channelInfo(id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { info(.ChannelsInfo, id: id, success: { (channel) -> Void in success(channel: channel) @@ -136,7 +136,7 @@ public class SlackWebAPI { } } - public func getChannelsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func channelsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { list(.ChannelsList, excludeArchived: excludeArchived, success: { (channels) -> Void in success(channels: channels) @@ -183,14 +183,8 @@ public class SlackWebAPI { } public func sendMessage(channel: String, text: String, username: String? = nil, asUser: Bool = false, parse: ParseMode = .Full, linkNames: Bool = false, attachments: [[String: AnyObject]]? = nil, unfurlLinks: Bool = false, unfurlMedia: Bool = false, iconURL: String? = nil, iconEmoji: String? = nil, success: ((ts: String?, channel: String?))->Void, failure: (error: SlackError)->Void) { - var parameters: [String: AnyObject] = ["channel":channel, "text":text.slackFormatEscaping(), "as_user":asUser, "parse":parse.rawValue, "link_names":linkNames, "unfurl_links":unfurlLinks, "unfurlMedia":unfurlMedia] - let optionalParameters: [String: AnyObject?] = ["username":username, "attachments":attachments, "icon_url":iconURL, "icon_emoji":iconEmoji] - for key in optionalParameters.keys { - if optionalParameters[key] != nil { - parameters[key] = optionalParameters[key]! - } - } - client.api.request(.ChatPostMessage, token: client.token, parameters: parameters, successClosure: { + let parameters: [String: AnyObject?] = ["channel":channel, "text":text.slackFormatEscaping(), "as_user":asUser, "parse":parse.rawValue, "link_names":linkNames, "unfurl_links":unfurlLinks, "unfurlMedia":unfurlMedia, "username":username, "attachments":attachments, "icon_url":iconURL, "icon_emoji":iconEmoji] + client.api.request(.ChatPostMessage, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in success((ts: response["ts"] as? String, response["channel"] as? String)) }) {(error) -> Void in @@ -199,11 +193,8 @@ public class SlackWebAPI { } public func updateMessage(channel: String, ts: String, message: String, attachments: [[String: AnyObject]]? = nil, parse:ParseMode = .None, linkNames: Bool = false, success: (updated: Bool)->Void, failure: (error: SlackError)->Void) { - var parameters: [String: AnyObject] = ["channel": channel, "ts": ts, "text": message, "parse": parse.rawValue, "link_names": linkNames] - if let attachments = attachments { - parameters["attachments"] = attachments - } - client.api.request(.ChatUpdate, token: client.token, parameters: parameters, successClosure: { + let parameters: [String: AnyObject?] = ["channel": channel, "ts": ts, "text": message.slackFormatEscaping(), "parse": parse.rawValue, "link_names": linkNames, "attachments":attachments] + client.api.request(.ChatUpdate, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in success(updated: true) }) {(error) -> Void in @@ -223,7 +214,8 @@ public class SlackWebAPI { //MARK: - Files public func deleteFile(fileID: String, success: (deleted: Bool)->Void, failure: (error: SlackError)->Void) { - client.api.request(.FilesDelete, token: client.token, parameters: ["file":fileID], successClosure: { + let parameters = ["file":fileID] + client.api.request(.FilesDelete, token: client.token, parameters: parameters, successClosure: { (response) -> Void in success(deleted: true) }) {(error) -> Void in @@ -231,8 +223,15 @@ public class SlackWebAPI { } } - public func uploadFile() { - //TODO: Upload file + public func uploadFile(file: NSData, filename: String, content: NSData? = nil, filetype: String = "auto", title: String? = nil, initialComment: String? = nil, channels: [String]? = nil, success: (file: File?)->Void, failure: (error: SlackError)->Void) { + //TODO: BROKEN, FIX THIS + let parameters: [String: AnyObject?] = ["file":file, "filename": filename, "filetype":filetype, "content":content, "title":title, "initial_comment":initialComment, "channels":channels?.joinWithSeparator(",")] + client.api.request(.FilesUpload, token: client.token, parameters: filterNilParameters(parameters), successClosure: { + (response) -> Void in + success(file: File(file: response["file"] as? [String: AnyObject])) + }) {(error) -> Void in + failure(error: error) + } } //MARK: - Groups @@ -245,7 +244,7 @@ public class SlackWebAPI { } } - public func getHistoryForGroup(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func groupHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { history(.GroupsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in success(history: history) @@ -254,7 +253,7 @@ public class SlackWebAPI { } } - public func getGroupInfo(id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { + public func groupInfo(id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { info(.GroupsInfo, id: id, success: { (channel) -> Void in success(channel: channel) @@ -263,7 +262,7 @@ public class SlackWebAPI { } } - public func getGroupsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func groupsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { list(.GroupsList, excludeArchived: excludeArchived, success: { (channels) -> Void in success(channels: channels) @@ -282,7 +281,8 @@ public class SlackWebAPI { } public func openGroup(channel: String, success: (opened: Bool)->Void, failure: (error: SlackError)->Void) { - client.api.request(.GroupsOpen, token: client.token, parameters: ["channel":channel], successClosure: { + let parameters = ["channel":channel] + client.api.request(.GroupsOpen, token: client.token, parameters: parameters, successClosure: { (response) -> Void in success(opened: true) }) {(error) -> Void in @@ -318,7 +318,7 @@ public class SlackWebAPI { } } - public func getHistoryForIM(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func imHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { history(.IMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in success(history: history) @@ -327,7 +327,7 @@ public class SlackWebAPI { } } - public func getIMList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func imsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { list(.IMList, excludeArchived: excludeArchived, success: { (channels) -> Void in success(channels: channels) @@ -346,7 +346,8 @@ public class SlackWebAPI { } public func openIM(userID: String, success: (imID: String?)->Void, failure: (error: SlackError)->Void) { - client.api.request(.IMOpen, token: client.token, parameters: ["user":userID], successClosure: { + let parameters = ["user":userID] + client.api.request(.IMOpen, token: client.token, parameters: parameters, successClosure: { (response) -> Void in let group = response["channel"] as? [String: AnyObject] success(imID: group?["id"] as? String) @@ -365,7 +366,7 @@ public class SlackWebAPI { } } - public func getHistoryForMPIM(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func mpimHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { history(.MPIMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in success(history: history) @@ -374,7 +375,7 @@ public class SlackWebAPI { } } - public func getMPIMList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func mpimsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { list(.MPIMList, excludeArchived: excludeArchived, success: { (channels) -> Void in success(channels: channels) @@ -393,8 +394,8 @@ public class SlackWebAPI { } public func openMPIM(userIDs: [String], success: (imID: String?)->Void, failure: (error: SlackError)->Void) { - let users = userIDs.joinWithSeparator(",") - client.api.request(.MPIMOpen, token: client.token, parameters: ["users":users], successClosure: { + let parameters = ["users":userIDs.joinWithSeparator(",")] + client.api.request(.MPIMOpen, token: client.token, parameters: parameters, successClosure: { (response) -> Void in let group = response["group"] as? [String: AnyObject] success(imID: group?["id"] as? String) @@ -405,32 +406,28 @@ public class SlackWebAPI { //MARK: - Pins public func pinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: (pinned: Bool)->Void, failure: (error: SlackError)->Void) { - var parameters: [String: AnyObject] = ["channel":channel] - let optionalParameters = ["file":file, "file_comment":fileComment, "timestamp":timestamp] - for key in optionalParameters.keys { - if optionalParameters[key] != nil { - parameters[key] = optionalParameters[key]! - } - } - client.api.request(.PinsAdd, token: client.token, parameters: parameters, successClosure: { - (response) -> Void in - success(pinned: true) - }){(error) -> Void in + pin(.PinsAdd, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: { + (ok) -> Void in + success(pinned: ok) + }) {(error) -> Void in failure(error: error) } } public func unpinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: (unpinned: Bool)->Void, failure: (error: SlackError)->Void) { - var parameters: [String: AnyObject] = ["channel":channel] - let optionalParameters = ["file":file, "file_comment":fileComment, "timestamp":timestamp] - for key in optionalParameters.keys { - if optionalParameters[key] != nil { - parameters[key] = optionalParameters[key]! - } + pin(.PinsRemove, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: { + (ok) -> Void in + success(unpinned: ok) + }) {(error) -> Void in + failure(error: error) } - client.api.request(.PinsRemove, token: client.token, parameters: parameters, successClosure: { + } + + private func pin(endpoint: SlackAPIEndpoint, channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: (ok: Bool)->Void, failure: (error: SlackError)->Void) { + let parameters: [String: AnyObject?] = ["channel":channel, "file":file, "file_comment":fileComment, "timestamp":timestamp] + client.api.request(endpoint, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success(unpinned: true) + success(ok: true) }){(error) -> Void in failure(error: error) } @@ -439,16 +436,9 @@ public class SlackWebAPI { //MARK: - Reactions // One of file, file_comment, or the combination of channel and timestamp must be specified. public func addReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (reacted: Bool)->Void, failure: (error: SlackError)->Void) { - var parameters: [String: AnyObject] = ["name":name] - let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] - for key in optionalParameters.keys { - if optionalParameters[key] != nil { - parameters[key] = optionalParameters[key]! - } - } - client.api.request(.ReactionsAdd, token: client.token, parameters: parameters, successClosure: { - (response) -> Void in - success(reacted: true) + react(.ReactionsAdd, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { + (ok) -> Void in + success(reacted: ok) }) {(error) -> Void in failure(error: error) } @@ -456,16 +446,19 @@ public class SlackWebAPI { // One of file, file_comment, or the combination of channel and timestamp must be specified. public func removeReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (unreacted: Bool)->Void, failure: (error: SlackError)->Void) { - var parameters: [String: AnyObject] = ["name":name] - let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] - for key in optionalParameters.keys { - if optionalParameters[key] != nil { - parameters[key] = optionalParameters[key]! - } + react(.ReactionsAdd, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { + (ok) -> Void in + success(unreacted: ok) + }) {(error) -> Void in + failure(error: error) } - client.api.request(.ReactionsRemove, token: client.token, parameters: parameters, successClosure: { + } + + private func react(endpoint: SlackAPIEndpoint, name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (ok: Bool)->Void, failure: (error: SlackError)->Void) { + let parameters: [String: AnyObject?] = ["name":name, "file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] + client.api.request(endpoint, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success(unreacted: true) + success(ok: true) }) {(error) -> Void in failure(error: error) } @@ -473,38 +466,35 @@ public class SlackWebAPI { //MARK: - Stars // One of file, file_comment, channel, or the combination of channel and timestamp must be specified. - public func addStar(file: String, fileComment: String, channel: String, timestamp: String, success: (starred: Bool)->Void, failure: (error: SlackError)->Void) { - var parameters = [String: AnyObject]() - let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] - for key in optionalParameters.keys { - if optionalParameters[key] != nil { - parameters[key] = optionalParameters[key]! - } - } - client.api.request(.StarsAdd, token: client.token, parameters: parameters, successClosure: { - (response) -> Void in - success(starred: true) + public func addStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (starred: Bool)->Void, failure: (error: SlackError)->Void) { + star(.StarsRemove, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { + (ok) -> Void in + success(starred: ok) }) {(error) -> Void in failure(error: error) } } // One of file, file_comment, channel, or the combination of channel and timestamp must be specified. - public func removeStar(file: String, fileComment: String, channel: String, timestamp: String, success: (unstarred: Bool)->Void, failure: (error: SlackError)->Void) { - var parameters = [String: AnyObject]() - let optionalParameters = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] - for key in optionalParameters.keys { - if optionalParameters[key] != nil { - parameters[key] = optionalParameters[key]! - } + public func removeStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (unstarred: Bool)->Void, failure: (error: SlackError)->Void) { + star(.StarsRemove, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { + (ok) -> Void in + success(unstarred: ok) + }) {(error) -> Void in + failure(error: error) } - client.api.request(.StarsRemove, token: client.token, parameters: parameters, successClosure: { + } + + private func star(endpoint: SlackAPIEndpoint, file: String?, fileComment: String?, channel: String?, timestamp: String?, success: (ok: Bool)->Void, failure: (error: SlackError)->Void) { + let parameters: [String: AnyObject?] = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] + client.api.request(endpoint, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success(unstarred: true) + success(ok: true) }) {(error) -> Void in failure(error: error) } } + //MARK: - Team public func getTeamInfo(success: (info: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { @@ -518,7 +508,8 @@ public class SlackWebAPI { //MARK: - Users public func getUserPresence(user: String, success: (presence: String?)->Void, failure: (error: SlackError)->Void) { - client.api.request(.UsersGetPresence, token: client.token, parameters: ["user":user], successClosure: { + let parameters: [String: AnyObject] = ["user":user] + client.api.request(.UsersGetPresence, token: client.token, parameters: parameters, successClosure: { (response) -> Void in success(presence: response["presence"] as? String) }){(error) -> Void in @@ -527,7 +518,8 @@ public class SlackWebAPI { } public func getUserInfo(id: String, success: (user: User?)->Void, failure: (error: SlackError)->Void) { - client.api.request(.UsersInfo, token: client.token, parameters: ["user":id], successClosure: { + let parameters: [String: AnyObject] = ["user":id] + client.api.request(.UsersInfo, token: client.token, parameters: parameters, successClosure: { (response) -> Void in success(user: User(user: response["user"] as? [String: AnyObject])) }) {(error) -> Void in @@ -536,7 +528,8 @@ public class SlackWebAPI { } public func getUserList(includePresence: Bool = false, success: (userList: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { - client.api.request(.UsersList, token: client.token, parameters: ["presence":includePresence], successClosure: { + let parameters: [String: AnyObject] = ["presence":includePresence] + client.api.request(.UsersList, token: client.token, parameters: parameters, successClosure: { (response) -> Void in success(userList: response["members"] as? [[String: AnyObject]]) }){(error) -> Void in @@ -554,7 +547,8 @@ public class SlackWebAPI { } public func setUserPresence(presence: Presence, success: (success: Bool)->Void, failure: (error: SlackError)->Void) { - client.api.request(.UsersSetPresence, token: client.token, parameters: ["presence":presence.rawValue], successClosure: { + let parameters: [String: AnyObject] = ["presence":presence.rawValue] + client.api.request(.UsersSetPresence, token: client.token, parameters: parameters, successClosure: { (response) -> Void in success(success:true) }) {(error) -> Void in @@ -564,7 +558,8 @@ public class SlackWebAPI { //MARK: - Channel Utilities private func close(endpoint: SlackAPIEndpoint, channelID: String, success: (closed: Bool)->Void, failure: (error: SlackError)->Void) { - client.api.request(endpoint, token: client.token, parameters: ["channel":channelID], successClosure: { + let parameters: [String: AnyObject] = ["channel":channelID] + client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in success(closed: true) }) {(error) -> Void in @@ -583,7 +578,8 @@ public class SlackWebAPI { } private func info(endpoint: SlackAPIEndpoint, id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { - client.api.request(endpoint, token: client.token, parameters: ["channel": id], successClosure: { + let parameters: [String: AnyObject] = ["channel": id] + client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in success(channel: Channel(channel: response["channel"] as? [String: AnyObject])) }) {(error) -> Void in @@ -621,4 +617,14 @@ public class SlackWebAPI { } } + //MARK: - Filter Nil Parameters + private func filterNilParameters(parameters: [String: AnyObject?]) -> [String: AnyObject] { + var finalParameters = [String: AnyObject]() + for key in parameters.keys { + if parameters[key] != nil { + finalParameters[key] = parameters[key]! + } + } + return finalParameters + } } From 96543932af5659f77b5bd6a21925ddbd40f694f3 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 16:35:39 -0500 Subject: [PATCH 16/26] General clean up --- SlackKit/Sources/Client.swift | 2 +- SlackKit/Sources/ClientExtensions.swift | 2 +- SlackKit/Sources/NetworkInterface.swift | 2 +- SlackKit/Sources/SlackWebAPIErrorDispatcher.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index 4efa23e..4f25700 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -69,7 +69,7 @@ public class Client: WebSocketDelegate { public func connect() { dispatcher = EventDispatcher(client: self) slackWebAPI = SlackWebAPI(client: self) - slackWebAPI?.connect(success: { + slackWebAPI?.rtmStart(success: { (response) -> Void in self.initialSetup(response) if let socketURL = response["url"] as? String { diff --git a/SlackKit/Sources/ClientExtensions.swift b/SlackKit/Sources/ClientExtensions.swift index 1a62f1e..60bfa99 100644 --- a/SlackKit/Sources/ClientExtensions.swift +++ b/SlackKit/Sources/ClientExtensions.swift @@ -1,5 +1,5 @@ // -// ClientExtensions.swift +// ClientExtensions.swift // // Copyright © 2016 Peter Zignego. All rights reserved. // diff --git a/SlackKit/Sources/NetworkInterface.swift b/SlackKit/Sources/NetworkInterface.swift index c4b546d..7c0205f 100644 --- a/SlackKit/Sources/NetworkInterface.swift +++ b/SlackKit/Sources/NetworkInterface.swift @@ -1,5 +1,5 @@ // -// NetworkInterface.swift +// NetworkInterface.swift // // Copyright © 2016 Peter Zignego. All rights reserved. // diff --git a/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift b/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift index 31e9670..940eaf1 100644 --- a/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift +++ b/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift @@ -1,5 +1,5 @@ // -// SlackWebAPIErrorHandling.swift +// SlackWebAPIErrorHandling.swift // // Copyright © 2016 Peter Zignego. All rights reserved. // From 38a762c60b81acc230df75afdc0e6b2d0a48b242 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 19:44:05 -0500 Subject: [PATCH 17/26] File upload --- SlackKit/Sources/NetworkInterface.swift | 56 +++++++++++++++++++++++++ SlackKit/Sources/SlackWebAPI.swift | 7 ++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/SlackKit/Sources/NetworkInterface.swift b/SlackKit/Sources/NetworkInterface.swift index 7c0205f..b2e6d45 100644 --- a/SlackKit/Sources/NetworkInterface.swift +++ b/SlackKit/Sources/NetworkInterface.swift @@ -59,6 +59,62 @@ internal struct NetworkInterface { }.resume() } + internal func uploadRequest(token: String, data: NSData, parameters: [String: AnyObject]?, successClosure: ([String: AnyObject])->Void, errorClosure: (SlackError)->Void) { + var requestString = "\(apiUrl)\(SlackAPIEndpoint.FilesUpload.rawValue)?token=\(token)" + if let params = parameters { + requestString = requestString + requestStringFromParameters(params) + } + + let request = NSMutableURLRequest(URL: NSURL(string: requestString)!) + request.HTTPMethod = "POST" + let boundaryConstant = randomBoundary() + let contentType = "multipart/form-data; boundary=" + boundaryConstant + let boundaryStart = "--\(boundaryConstant)\r\n" + let boundaryEnd = "--\(boundaryConstant)--\r\n" + let contentDispositionString = "Content-Disposition: form-data; name=\"file\"; filename=\"\(parameters!["filename"])\"\r\n" + let contentTypeString = "Content-Type: \(parameters!["filetype"])\r\n\r\n" + + let requestBodyData : NSMutableData = NSMutableData() + requestBodyData.appendData(boundaryStart.dataUsingEncoding(NSUTF8StringEncoding)!) + requestBodyData.appendData(contentDispositionString.dataUsingEncoding(NSUTF8StringEncoding)!) + requestBodyData.appendData(contentTypeString.dataUsingEncoding(NSUTF8StringEncoding)!) + requestBodyData.appendData(data) + requestBodyData.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) + requestBodyData.appendData(boundaryEnd.dataUsingEncoding(NSUTF8StringEncoding)!) + + request.setValue(contentType, forHTTPHeaderField: "Content-Type") + request.HTTPBody = requestBodyData + + NSURLSession.sharedSession().dataTaskWithRequest(request) { + (data, response, internalError) -> Void in + guard let data = data else { + return + } + do { + let result = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject] + if (result["ok"] as! Bool == true) { + successClosure(result) + } else { + if let errorString = result["error"] as? String { + throw ErrorDispatcher.dispatch(errorString) + } else { + throw SlackError.UnknownError + } + } + } catch let error { + if let slackError = error as? SlackError { + errorClosure(slackError) + } else { + errorClosure(SlackError.UnknownError) + } + } + }.resume() + } + + private func randomBoundary() -> String { + return String(format: "slackkit.boundary.%08x%08x", arc4random(), arc4random()) + } + private func requestStringFromParameters(parameters: [String: AnyObject]) -> String { var requestString = "" for key in parameters.keys { diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index e60b282..7e9b22c 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -223,10 +223,9 @@ public class SlackWebAPI { } } - public func uploadFile(file: NSData, filename: String, content: NSData? = nil, filetype: String = "auto", title: String? = nil, initialComment: String? = nil, channels: [String]? = nil, success: (file: File?)->Void, failure: (error: SlackError)->Void) { - //TODO: BROKEN, FIX THIS - let parameters: [String: AnyObject?] = ["file":file, "filename": filename, "filetype":filetype, "content":content, "title":title, "initial_comment":initialComment, "channels":channels?.joinWithSeparator(",")] - client.api.request(.FilesUpload, token: client.token, parameters: filterNilParameters(parameters), successClosure: { + public func uploadFile(file: NSData, filename: String, filetype: String = "auto", title: String? = nil, initialComment: String? = nil, channels: [String]? = nil, success: (file: File?)->Void, failure: (error: SlackError)->Void) { + let parameters: [String: AnyObject?] = ["file":file, "filename": filename, "filetype":filetype, "title":title, "initial_comment":initialComment, "channels":channels?.joinWithSeparator(",")] + client.api.uploadRequest(client.token, data: file, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in success(file: File(file: response["file"] as? [String: AnyObject])) }) {(error) -> Void in From 0a203bbc683e6e4785dc4cc555dd699223be2fd9 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 20:34:41 -0500 Subject: [PATCH 18/26] Web api clean up --- SlackKit/Sources/SlackWebAPI.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index 7e9b22c..71d07a1 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -496,7 +496,7 @@ public class SlackWebAPI { //MARK: - Team - public func getTeamInfo(success: (info: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func teamInfo(success: (info: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { client.api.request(.TeamInfo, token: client.token, parameters: nil, successClosure: { (response) -> Void in success(info: response["team"] as? [String: AnyObject]) @@ -506,7 +506,7 @@ public class SlackWebAPI { } //MARK: - Users - public func getUserPresence(user: String, success: (presence: String?)->Void, failure: (error: SlackError)->Void) { + public func userPresence(user: String, success: (presence: String?)->Void, failure: (error: SlackError)->Void) { let parameters: [String: AnyObject] = ["user":user] client.api.request(.UsersGetPresence, token: client.token, parameters: parameters, successClosure: { (response) -> Void in @@ -516,7 +516,7 @@ public class SlackWebAPI { } } - public func getUserInfo(id: String, success: (user: User?)->Void, failure: (error: SlackError)->Void) { + public func userInfo(id: String, success: (user: User?)->Void, failure: (error: SlackError)->Void) { let parameters: [String: AnyObject] = ["user":id] client.api.request(.UsersInfo, token: client.token, parameters: parameters, successClosure: { (response) -> Void in @@ -526,7 +526,7 @@ public class SlackWebAPI { } } - public func getUserList(includePresence: Bool = false, success: (userList: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func userList(includePresence: Bool = false, success: (userList: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { let parameters: [String: AnyObject] = ["presence":includePresence] client.api.request(.UsersList, token: client.token, parameters: parameters, successClosure: { (response) -> Void in From 82978bb9636bf42cd05cae04ccb151d7afa02afa Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 20:35:03 -0500 Subject: [PATCH 19/26] Add additional errors --- .../Sources/SlackWebAPIErrorDispatcher.swift | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift b/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift index 940eaf1..798b5fd 100644 --- a/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift +++ b/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift @@ -24,15 +24,25 @@ import Foundation public enum SlackError: ErrorType { - case AlreadyArchived case AccountInactive + case AlreadyArchived + case AlreadyInChannel case AlreadyPinned case AlreadyReacted case AlreadyStarred + case BadClientSecret + case BadRedirectURI case BadTimeStamp case CantArchiveGeneral case CantDeleteFile case CantDeleteMessage + case CantInvite + case CantInviteSelf + case CantKickFromGeneral + case CantKickFromLastChannel + case CantKickSelf + case CantLeaveGeneral + case CantLeaveLastChannel case CantUpdateMessage case ChannelNotFound case ComplianceExportsPreventDeletion @@ -41,40 +51,61 @@ public enum SlackError: ErrorType { case FileDeleted case FileNotFound case FileNotShared + case GroupContainsOthers case InvalidArrayArg case InvalidAuth case InvalidChannel + case InvalidCharSet + case InvalidClientID + case InvalidCode + case InvalidFormData case InvalidName + case InvalidPostType case InvalidPresence case InvalidTS case InvalidTSLatest case InvalidTSOldest case IsArchived + case LastMember case LastRAChannel case MessageNotFound case MessageTooLong case MigrationInProgress + case MissingDuration + case MissingPostType + case NameTaken + case NoChannel case NoItemSpecified case NoReaction case NoText + case NotArchived case NotAuthed case NotEnoughUsers case NotInChannel + case NotInGroup case NotPinned case NotStarred + case OverPaginationLimit + case PaidOnly case PermissionDenied case PostingToGeneralChannelDenied case RateLimited case RequestTimeout case RestrictedAction + case SnoozeEndFailed + case SnoozeFailed + case SnoozeNotActive case TooLong case TooManyEmoji case TooManyReactions case TooManyUsers case UnknownError + case UnknownType case UserDisabled case UserDoesNotOwnChannel + case UserIsBot case UserIsRestricted + case UserIsUltraRestricted case UserListNotSupplied case UserNotFound case UserNotVisible @@ -86,18 +117,38 @@ internal struct ErrorDispatcher { switch error { case "account_inactive": return .AccountInactive + case "already_in_channel": + return .AlreadyInChannel case "already_pinned": return .AlreadyPinned case "already_reacted": return .AlreadyReacted case "already_starred": return .AlreadyStarred + case "bad_client_secret": + return .BadClientSecret + case "bad_redirect_uri": + return .BadRedirectURI case "bad_timestamp": return .BadTimeStamp case "cant_delete_file": return .CantDeleteFile case "cant_delete_message": return .CantDeleteMessage + case "cant_invite": + return .CantInvite + case "cant_invite_self": + return .CantInviteSelf + case "cant_kick_from_general": + return .CantKickFromGeneral + case "cant_kick_from_last_channel": + return .CantKickFromLastChannel + case "cant_kick_self": + return .CantKickSelf + case "cant_leave_general": + return .CantLeaveGeneral + case "cant_leave_last_channel": + return .CantLeaveLastChannel case "cant_update_message": return .CantUpdateMessage case "compliance_exports_prevent_deletion": @@ -114,14 +165,26 @@ internal struct ErrorDispatcher { return .FileNotFound case "file_not_shared": return .FileNotShared + case "group_contains_others": + return .GroupContainsOthers case "invalid_array_arg": return .InvalidArrayArg case "invalid_auth": return .InvalidAuth case "invalid_channel": return .InvalidChannel + case "invalid_charset": + return .InvalidCharSet + case "invalid_client_id": + return .InvalidClientID + case "invalid_code": + return .InvalidCode + case "invalid_form_data": + return .InvalidFormData case "invalid_name": return .InvalidName + case "invalid_post_type": + return .InvalidPostType case "invalid_presence": return .InvalidPresence case "invalid_timestamp": @@ -132,28 +195,48 @@ internal struct ErrorDispatcher { return .InvalidTSOldest case "is_archived": return .IsArchived + case "last_member": + return .LastMember + case "last_ra_channel": + return .LastRAChannel case "message_not_found": return .MessageNotFound case "msg_too_long": return .MessageTooLong case "migration_in_progress": return .MigrationInProgress + case "missing_duration": + return .MissingDuration + case "missing_post_type": + return .MissingPostType + case "name_taken": + return .NameTaken + case "no_channel": + return .NoChannel case "no_reaction": return .NoReaction case "no_item_specified": return .NoItemSpecified case "no_text": return .NoText + case "not_archived": + return .NotArchived case "not_authed": return .NotAuthed case "not_enough_users": return .NotEnoughUsers case "not_in_channel": return .NotInChannel + case "not_in_group": + return .NotInGroup case "not_pinned": return .NotPinned case "not_starred": return .NotStarred + case "over_pagination_limit": + return .OverPaginationLimit + case "paid_only": + return .PaidOnly case "perimssion_denied": return .PermissionDenied case "posting_to_general_channel_denied": @@ -162,6 +245,12 @@ internal struct ErrorDispatcher { return .RateLimited case "request_timeout": return .RequestTimeout + case "snooze_end_failed": + return .SnoozeEndFailed + case "snooze_failed": + return .SnoozeFailed + case "snooze_not_active": + return .SnoozeNotActive case "too_long": return .TooLong case "too_many_emoji": @@ -170,12 +259,18 @@ internal struct ErrorDispatcher { return .TooManyReactions case "too_many_users": return .TooManyUsers + case "unknown_type": + return .UnknownType case "user_disabled": return .UserDisabled case "user_does_not_own_channel": return .UserDoesNotOwnChannel + case "user_is_bot": + return .UserIsBot case "user_is_restricted": return .UserIsRestricted + case "user_is_ultra_restricted": + return .UserIsUltraRestricted case "user_list_not_supplied": return .UserListNotSupplied case "user_not_found": From 5550658d83cfebdcd9a00c0b45130f2970686779 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 21:08:51 -0500 Subject: [PATCH 20/26] Encode parameters --- SlackKit/Sources/NetworkInterface.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SlackKit/Sources/NetworkInterface.swift b/SlackKit/Sources/NetworkInterface.swift index b2e6d45..47bcba5 100644 --- a/SlackKit/Sources/NetworkInterface.swift +++ b/SlackKit/Sources/NetworkInterface.swift @@ -118,8 +118,8 @@ internal struct NetworkInterface { private func requestStringFromParameters(parameters: [String: AnyObject]) -> String { var requestString = "" for key in parameters.keys { - if let value = parameters[key] as? String { - requestString = requestString + "&\(key)=\(value)" + if let value = parameters[key] as? String, encodedValue = value.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet()) { + requestString = requestString + "&"+key+"="+encodedValue } } From dee376e1b989af58ddaa4573d45a97b5ebd476bf Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 21:09:07 -0500 Subject: [PATCH 21/26] Make success and failure closures optional --- SlackKit/Sources/SlackWebAPI.swift | 322 +++++++++++++++-------------- 1 file changed, 162 insertions(+), 160 deletions(-) diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index 71d07a1..c1f9e6e 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -75,7 +75,9 @@ internal enum SlackAPIEndpoint: String { public class SlackWebAPI { - + + public typealias FailureClosure = (error: SlackError)->Void + public enum InfoType: String { case Purpose = "purpose" case Topic = "topic" @@ -98,521 +100,521 @@ public class SlackWebAPI { } //MARK: - RTM - public func rtmStart(success success: (response: [String: AnyObject])->Void, failure: (error: SlackError)->Void) { + public func rtmStart(success success: ((response: [String: AnyObject])->Void)?, failure: FailureClosure?) { client.api.request(.RTMStart, token: client.token, parameters: nil, successClosure: { (response) -> Void in - success(response: response) + success?(response: response) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Auth Test - public func authenticationTest(success: (authenticated: Bool)->Void, failure: (error: SlackError)->Void) { + public func authenticationTest(success: ((authenticated: Bool)->Void)?, failure: FailureClosure?) { client.api.request(.AuthTest, token: client.token, parameters: nil, successClosure: { (response) -> Void in - success(authenticated: true) + success?(authenticated: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Channels - public func channelHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func channelHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: [String: AnyObject]?)->Void)?, failure: FailureClosure?) { history(.ChannelsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in - success(history:history) + success?(history:history) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func channelInfo(id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { + public func channelInfo(id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) { info(.ChannelsInfo, id: id, success: { (channel) -> Void in - success(channel: channel) + success?(channel: channel) }) { (error) -> Void in - failure(error: error) + failure?(error: error) } } - public func channelsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func channelsList(excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { list(.ChannelsList, excludeArchived: excludeArchived, success: { (channels) -> Void in - success(channels: channels) + success?(channels: channels) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func markChannel(channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { + public func markChannel(channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { mark(.ChannelsMark, channel: channel, timestamp: timestamp, success: { (marked) -> Void in - success(marked:marked) + success?(marked:marked) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func setChannelPurpose(channel: String, purpose: String, success: (purposeSet: Bool)->Void, failure: (error: SlackError)->Void) { + public func setChannelPurpose(channel: String, purpose: String, success: ((purposeSet: Bool)->Void)?, failure: FailureClosure?) { setInfo(.ChannelsSetPurpose, type: .Purpose, channel: channel, text: purpose, success: { (purposeSet) -> Void in - success(purposeSet: purposeSet) + success?(purposeSet: purposeSet) }) { (error) -> Void in - failure(error: error) + failure?(error: error) } } - public func setChannelTopic(channel: String, topic: String, success: (topicSet: Bool)->Void, failure: (error: SlackError)->Void) { + public func setChannelTopic(channel: String, topic: String, success: ((topicSet: Bool)->Void)?, failure: FailureClosure?) { setInfo(.ChannelsSetTopic, type: .Topic, channel: channel, text: topic, success: { (topicSet) -> Void in - success(topicSet: topicSet) + success?(topicSet: topicSet) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Messaging - public func deleteMessage(channel: String, ts: String, success: (deleted: Bool)->Void, failure: (error: SlackError)->Void) { + public func deleteMessage(channel: String, ts: String, success: ((deleted: Bool)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["channel": channel, "ts": ts] client.api.request(.ChatDelete, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(deleted: true) + success?(deleted: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func sendMessage(channel: String, text: String, username: String? = nil, asUser: Bool = false, parse: ParseMode = .Full, linkNames: Bool = false, attachments: [[String: AnyObject]]? = nil, unfurlLinks: Bool = false, unfurlMedia: Bool = false, iconURL: String? = nil, iconEmoji: String? = nil, success: ((ts: String?, channel: String?))->Void, failure: (error: SlackError)->Void) { + public func sendMessage(channel: String, text: String, username: String? = nil, asUser: Bool = false, parse: ParseMode = .Full, linkNames: Bool = false, attachments: [[String: AnyObject]]? = nil, unfurlLinks: Bool = false, unfurlMedia: Bool = false, iconURL: String? = nil, iconEmoji: String? = nil, success: (((ts: String?, channel: String?))->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject?] = ["channel":channel, "text":text.slackFormatEscaping(), "as_user":asUser, "parse":parse.rawValue, "link_names":linkNames, "unfurl_links":unfurlLinks, "unfurlMedia":unfurlMedia, "username":username, "attachments":attachments, "icon_url":iconURL, "icon_emoji":iconEmoji] client.api.request(.ChatPostMessage, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success((ts: response["ts"] as? String, response["channel"] as? String)) + success?((ts: response["ts"] as? String, response["channel"] as? String)) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func updateMessage(channel: String, ts: String, message: String, attachments: [[String: AnyObject]]? = nil, parse:ParseMode = .None, linkNames: Bool = false, success: (updated: Bool)->Void, failure: (error: SlackError)->Void) { + public func updateMessage(channel: String, ts: String, message: String, attachments: [[String: AnyObject]]? = nil, parse:ParseMode = .None, linkNames: Bool = false, success: ((updated: Bool)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject?] = ["channel": channel, "ts": ts, "text": message.slackFormatEscaping(), "parse": parse.rawValue, "link_names": linkNames, "attachments":attachments] client.api.request(.ChatUpdate, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success(updated: true) + success?(updated: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Emoji - public func emojiList(success: (emojiList: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func emojiList(success: ((emojiList: [String: AnyObject]?)->Void)?, failure: FailureClosure?) { client.api.request(.EmojiList, token: client.token, parameters: nil, successClosure: { (response) -> Void in - success(emojiList: response["emoji"] as? [String: AnyObject]) + success?(emojiList: response["emoji"] as? [String: AnyObject]) }) { (error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Files - public func deleteFile(fileID: String, success: (deleted: Bool)->Void, failure: (error: SlackError)->Void) { + public func deleteFile(fileID: String, success: ((deleted: Bool)->Void)?, failure: FailureClosure?) { let parameters = ["file":fileID] client.api.request(.FilesDelete, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(deleted: true) + success?(deleted: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func uploadFile(file: NSData, filename: String, filetype: String = "auto", title: String? = nil, initialComment: String? = nil, channels: [String]? = nil, success: (file: File?)->Void, failure: (error: SlackError)->Void) { + public func uploadFile(file: NSData, filename: String, filetype: String = "auto", title: String? = nil, initialComment: String? = nil, channels: [String]? = nil, success: ((file: File?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject?] = ["file":file, "filename": filename, "filetype":filetype, "title":title, "initial_comment":initialComment, "channels":channels?.joinWithSeparator(",")] client.api.uploadRequest(client.token, data: file, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success(file: File(file: response["file"] as? [String: AnyObject])) + success?(file: File(file: response["file"] as? [String: AnyObject])) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Groups - public func closeGroup(groupID: String, success: (closed: Bool)->Void, failure: (error: SlackError)->Void) { + public func closeGroup(groupID: String, success: ((closed: Bool)->Void)?, failure: FailureClosure?) { close(.GroupsClose, channelID: groupID, success: { (closed) -> Void in - success(closed:closed) + success?(closed:closed) }) {(error) -> Void in - failure(error:error) + failure?(error:error) } } - public func groupHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func groupHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: [String: AnyObject]?)->Void)?, failure: FailureClosure?) { history(.GroupsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in - success(history: history) + success?(history: history) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func groupInfo(id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { + public func groupInfo(id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) { info(.GroupsInfo, id: id, success: { (channel) -> Void in - success(channel: channel) + success?(channel: channel) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func groupsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func groupsList(excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { list(.GroupsList, excludeArchived: excludeArchived, success: { (channels) -> Void in - success(channels: channels) + success?(channels: channels) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func markGroup(channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { + public func markGroup(channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { mark(.GroupsMark, channel: channel, timestamp: timestamp, success: { (marked) -> Void in - success(marked: marked) + success?(marked: marked) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func openGroup(channel: String, success: (opened: Bool)->Void, failure: (error: SlackError)->Void) { + public func openGroup(channel: String, success: ((opened: Bool)->Void)?, failure: FailureClosure?) { let parameters = ["channel":channel] client.api.request(.GroupsOpen, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(opened: true) + success?(opened: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func setGroupPurpose(channel: String, purpose: String, success: (purposeSet: Bool)->Void, failure: (error: SlackError)->Void) { + public func setGroupPurpose(channel: String, purpose: String, success: ((purposeSet: Bool)->Void)?, failure: FailureClosure?) { setInfo(.GroupsSetPurpose, type: .Purpose, channel: channel, text: purpose, success: { (purposeSet) -> Void in - success(purposeSet: purposeSet) + success?(purposeSet: purposeSet) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func setGroupTopic(channel: String, topic: String, success: (topicSet: Bool)->Void, failure: (error: SlackError)->Void) { + public func setGroupTopic(channel: String, topic: String, success: ((topicSet: Bool)->Void)?, failure: FailureClosure?) { setInfo(.GroupsSetTopic, type: .Topic, channel: channel, text: topic, success: { (topicSet) -> Void in - success(topicSet: topicSet) + success?(topicSet: topicSet) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - IM - public func closeIM(channel: String, success: (closed: Bool)->Void, failure: (error: SlackError)->Void) { + public func closeIM(channel: String, success: ((closed: Bool)->Void)?, failure: FailureClosure?) { close(.IMClose, channelID: channel, success: { (closed) -> Void in - success(closed: closed) + success?(closed: closed) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func imHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func imHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: [String: AnyObject]?)->Void)?, failure: FailureClosure?) { history(.IMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in - success(history: history) + success?(history: history) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func imsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func imsList(excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { list(.IMList, excludeArchived: excludeArchived, success: { (channels) -> Void in - success(channels: channels) + success?(channels: channels) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func markIM(channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { + public func markIM(channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { mark(.IMMark, channel: channel, timestamp: timestamp, success: { (marked) -> Void in - success(marked: marked) + success?(marked: marked) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func openIM(userID: String, success: (imID: String?)->Void, failure: (error: SlackError)->Void) { + public func openIM(userID: String, success: ((imID: String?)->Void)?, failure: FailureClosure?) { let parameters = ["user":userID] client.api.request(.IMOpen, token: client.token, parameters: parameters, successClosure: { (response) -> Void in let group = response["channel"] as? [String: AnyObject] - success(imID: group?["id"] as? String) + success?(imID: group?["id"] as? String) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - MPIM - public func closeMPIM(channel: String, success: (closed: Bool)->Void, failure: (error: SlackError)->Void) { + public func closeMPIM(channel: String, success: ((closed: Bool)->Void)?, failure: FailureClosure?) { close(.MPIMClose, channelID: channel, success: { (closed) -> Void in - success(closed: closed) + success?(closed: closed) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func mpimHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func mpimHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: [String: AnyObject]?)->Void)?, failure: FailureClosure?) { history(.MPIMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in - success(history: history) + success?(history: history) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func mpimsList(excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func mpimsList(excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { list(.MPIMList, excludeArchived: excludeArchived, success: { (channels) -> Void in - success(channels: channels) + success?(channels: channels) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func markMPIM(channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { + public func markMPIM(channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { mark(.MPIMMark, channel: channel, timestamp: timestamp, success: { (marked) -> Void in - success(marked: marked) + success?(marked: marked) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func openMPIM(userIDs: [String], success: (imID: String?)->Void, failure: (error: SlackError)->Void) { + public func openMPIM(userIDs: [String], success: ((imID: String?)->Void)?, failure: FailureClosure?) { let parameters = ["users":userIDs.joinWithSeparator(",")] client.api.request(.MPIMOpen, token: client.token, parameters: parameters, successClosure: { (response) -> Void in let group = response["group"] as? [String: AnyObject] - success(imID: group?["id"] as? String) + success?(imID: group?["id"] as? String) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Pins - public func pinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: (pinned: Bool)->Void, failure: (error: SlackError)->Void) { + public func pinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: ((pinned: Bool)->Void)?, failure: FailureClosure?) { pin(.PinsAdd, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: { (ok) -> Void in - success(pinned: ok) + success?(pinned: ok) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func unpinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: (unpinned: Bool)->Void, failure: (error: SlackError)->Void) { + public func unpinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: ((unpinned: Bool)->Void)?, failure: FailureClosure?) { pin(.PinsRemove, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: { (ok) -> Void in - success(unpinned: ok) + success?(unpinned: ok) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - private func pin(endpoint: SlackAPIEndpoint, channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: (ok: Bool)->Void, failure: (error: SlackError)->Void) { + private func pin(endpoint: SlackAPIEndpoint, channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: ((ok: Bool)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject?] = ["channel":channel, "file":file, "file_comment":fileComment, "timestamp":timestamp] client.api.request(endpoint, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success(ok: true) + success?(ok: true) }){(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Reactions // One of file, file_comment, or the combination of channel and timestamp must be specified. - public func addReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (reacted: Bool)->Void, failure: (error: SlackError)->Void) { + public func addReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((reacted: Bool)->Void)?, failure: FailureClosure?) { react(.ReactionsAdd, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { (ok) -> Void in - success(reacted: ok) + success?(reacted: ok) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } // One of file, file_comment, or the combination of channel and timestamp must be specified. - public func removeReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (unreacted: Bool)->Void, failure: (error: SlackError)->Void) { + public func removeReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((unreacted: Bool)->Void)?, failure: FailureClosure?) { react(.ReactionsAdd, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { (ok) -> Void in - success(unreacted: ok) + success?(unreacted: ok) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - private func react(endpoint: SlackAPIEndpoint, name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (ok: Bool)->Void, failure: (error: SlackError)->Void) { + private func react(endpoint: SlackAPIEndpoint, name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((ok: Bool)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject?] = ["name":name, "file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] client.api.request(endpoint, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success(ok: true) + success?(ok: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Stars // One of file, file_comment, channel, or the combination of channel and timestamp must be specified. - public func addStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (starred: Bool)->Void, failure: (error: SlackError)->Void) { + public func addStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((starred: Bool)->Void)?, failure: FailureClosure?) { star(.StarsRemove, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { (ok) -> Void in - success(starred: ok) + success?(starred: ok) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } // One of file, file_comment, channel, or the combination of channel and timestamp must be specified. - public func removeStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: (unstarred: Bool)->Void, failure: (error: SlackError)->Void) { + public func removeStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((unstarred: Bool)->Void)?, failure: FailureClosure?) { star(.StarsRemove, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { (ok) -> Void in - success(unstarred: ok) + success?(unstarred: ok) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - private func star(endpoint: SlackAPIEndpoint, file: String?, fileComment: String?, channel: String?, timestamp: String?, success: (ok: Bool)->Void, failure: (error: SlackError)->Void) { + private func star(endpoint: SlackAPIEndpoint, file: String?, fileComment: String?, channel: String?, timestamp: String?, success: ((ok: Bool)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject?] = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp] client.api.request(endpoint, token: client.token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success(ok: true) + success?(ok: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Team - public func teamInfo(success: (info: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + public func teamInfo(success: ((info: [String: AnyObject]?)->Void)?, failure: FailureClosure?) { client.api.request(.TeamInfo, token: client.token, parameters: nil, successClosure: { (response) -> Void in - success(info: response["team"] as? [String: AnyObject]) + success?(info: response["team"] as? [String: AnyObject]) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Users - public func userPresence(user: String, success: (presence: String?)->Void, failure: (error: SlackError)->Void) { + public func userPresence(user: String, success: ((presence: String?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["user":user] client.api.request(.UsersGetPresence, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(presence: response["presence"] as? String) + success?(presence: response["presence"] as? String) }){(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func userInfo(id: String, success: (user: User?)->Void, failure: (error: SlackError)->Void) { + public func userInfo(id: String, success: ((user: User?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["user":id] client.api.request(.UsersInfo, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(user: User(user: response["user"] as? [String: AnyObject])) + success?(user: User(user: response["user"] as? [String: AnyObject])) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func userList(includePresence: Bool = false, success: (userList: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + public func userList(includePresence: Bool = false, success: ((userList: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["presence":includePresence] client.api.request(.UsersList, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(userList: response["members"] as? [[String: AnyObject]]) + success?(userList: response["members"] as? [[String: AnyObject]]) }){(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func setUserActive(success: (success: Bool)->Void, failure: (error: SlackError)->Void) { + public func setUserActive(success: ((success: Bool)->Void)?, failure: FailureClosure?) { client.api.request(.UsersSetActive, token: client.token, parameters: nil, successClosure: { (response) -> Void in - success(success: true) + success?(success: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - public func setUserPresence(presence: Presence, success: (success: Bool)->Void, failure: (error: SlackError)->Void) { + public func setUserPresence(presence: Presence, success: ((success: Bool)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["presence":presence.rawValue] client.api.request(.UsersSetPresence, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(success:true) + success?(success:true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } //MARK: - Channel Utilities - private func close(endpoint: SlackAPIEndpoint, channelID: String, success: (closed: Bool)->Void, failure: (error: SlackError)->Void) { + private func close(endpoint: SlackAPIEndpoint, channelID: String, success: ((closed: Bool)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["channel":channelID] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(closed: true) + success?(closed: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - private func history(endpoint: SlackAPIEndpoint, id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: (history: [String: AnyObject]?)->Void, failure: (error: SlackError)->Void) { + private func history(endpoint: SlackAPIEndpoint, id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: [String: AnyObject]?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["channel": id, "latest": latest, "oldest": oldest, "inclusive":inclusive, "count":count, "unreads":unreads] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(history: response) + success?(history: response) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - private func info(endpoint: SlackAPIEndpoint, id: String, success: (channel: Channel?)->Void, failure: (error: SlackError)->Void) { + private func info(endpoint: SlackAPIEndpoint, id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["channel": id] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(channel: Channel(channel: response["channel"] as? [String: AnyObject])) + success?(channel: Channel(channel: response["channel"] as? [String: AnyObject])) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - private func list(endpoint: SlackAPIEndpoint, excludeArchived: Bool = false, success: (channels: [[String: AnyObject]]?)->Void, failure: (error: SlackError)->Void) { + private func list(endpoint: SlackAPIEndpoint, excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["exclude_archived": excludeArchived] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(channels: response["channels"] as? [[String: AnyObject]]) + success?(channels: response["channels"] as? [[String: AnyObject]]) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - private func mark(endpoint: SlackAPIEndpoint, channel: String, timestamp: String, success: (marked: Bool)->Void, failure: (error: SlackError)->Void) { + private func mark(endpoint: SlackAPIEndpoint, channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["channel": channel, "ts": timestamp] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(marked: true) + success?(marked: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } - private func setInfo(endpoint: SlackAPIEndpoint, type: InfoType, channel: String, text: String, success: (success: Bool)->Void, failure: (error: SlackError)->Void) { + private func setInfo(endpoint: SlackAPIEndpoint, type: InfoType, channel: String, text: String, success: ((success: Bool)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["channel": channel, type.rawValue: text] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success(success: true) + success?(success: true) }) {(error) -> Void in - failure(error: error) + failure?(error: error) } } From 78b73c4c99fcf5fa4ba58a6bf764380e42660017 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Sun, 14 Feb 2016 21:31:42 -0500 Subject: [PATCH 22/26] Bug fixes --- SlackKit/Sources/Client.swift | 3 +-- SlackKit/Sources/SlackWebAPI.swift | 27 ++++++++++++++++----------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index 4f25700..8c25c01 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -78,8 +78,7 @@ public class Client: WebSocketDelegate { self.webSocket?.delegate = self self.webSocket?.connect() } - }, failure: {(error) -> Void in - }) + }, failure:nil) } //MARK: - Message send diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index c1f9e6e..ae9c030 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -73,7 +73,6 @@ internal enum SlackAPIEndpoint: String { case UsersSetPresence = "users.setPresence" } - public class SlackWebAPI { public typealias FailureClosure = (error: SlackError)->Void @@ -93,6 +92,12 @@ public class SlackWebAPI { case Away = "away" } + private enum ChannelType: String { + case Channel = "channel" + case Group = "group" + case IM = "im" + } + private let client: Client required public init(client: Client) { @@ -130,7 +135,7 @@ public class SlackWebAPI { } public func channelInfo(id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) { - info(.ChannelsInfo, id: id, success: { + info(.ChannelsInfo, type:ChannelType.Channel, id: id, success: { (channel) -> Void in success?(channel: channel) }) { (error) -> Void in @@ -139,7 +144,7 @@ public class SlackWebAPI { } public func channelsList(excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { - list(.ChannelsList, excludeArchived: excludeArchived, success: { + list(.ChannelsList, type:ChannelType.Channel, excludeArchived: excludeArchived, success: { (channels) -> Void in success?(channels: channels) }) {(error) -> Void in @@ -255,7 +260,7 @@ public class SlackWebAPI { } public func groupInfo(id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) { - info(.GroupsInfo, id: id, success: { + info(.GroupsInfo, type:ChannelType.Group, id: id, success: { (channel) -> Void in success?(channel: channel) }) {(error) -> Void in @@ -264,7 +269,7 @@ public class SlackWebAPI { } public func groupsList(excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { - list(.GroupsList, excludeArchived: excludeArchived, success: { + list(.GroupsList, type:ChannelType.Group, excludeArchived: excludeArchived, success: { (channels) -> Void in success?(channels: channels) }) {(error) -> Void in @@ -329,7 +334,7 @@ public class SlackWebAPI { } public func imsList(excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { - list(.IMList, excludeArchived: excludeArchived, success: { + list(.IMList, type:ChannelType.IM, excludeArchived: excludeArchived, success: { (channels) -> Void in success?(channels: channels) }) {(error) -> Void in @@ -377,7 +382,7 @@ public class SlackWebAPI { } public func mpimsList(excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { - list(.MPIMList, excludeArchived: excludeArchived, success: { + list(.MPIMList, type:ChannelType.Group, excludeArchived: excludeArchived, success: { (channels) -> Void in success?(channels: channels) }) {(error) -> Void in @@ -578,21 +583,21 @@ public class SlackWebAPI { } } - private func info(endpoint: SlackAPIEndpoint, id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) { + private func info(endpoint: SlackAPIEndpoint, type: ChannelType, id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["channel": id] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success?(channel: Channel(channel: response["channel"] as? [String: AnyObject])) + success?(channel: Channel(channel: response[type.rawValue] as? [String: AnyObject])) }) {(error) -> Void in failure?(error: error) } } - private func list(endpoint: SlackAPIEndpoint, excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { + private func list(endpoint: SlackAPIEndpoint, type: ChannelType, excludeArchived: Bool = false, success: ((channels: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["exclude_archived": excludeArchived] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success?(channels: response["channels"] as? [[String: AnyObject]]) + success?(channels: response[type.rawValue+"s"] as? [[String: AnyObject]]) }) {(error) -> Void in failure?(error: error) } From 92d6d833ea13ecd0f7cdca272a1bc7dc235234c5 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Mon, 15 Feb 2016 20:13:10 -0500 Subject: [PATCH 23/26] Clean up --- RLS/Assets.xcassets/Contents.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 RLS/Assets.xcassets/Contents.json diff --git a/RLS/Assets.xcassets/Contents.json b/RLS/Assets.xcassets/Contents.json deleted file mode 100644 index da4a164..0000000 --- a/RLS/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file From 02eff541b14f1c659c1d2ac61ac7809a0c8f23ef Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Thu, 18 Feb 2016 12:57:53 -0500 Subject: [PATCH 24/26] Fixes --- SlackKit/Sources/Client.swift | 6 ++-- SlackKit/Sources/ClientExtensions.swift | 15 ++++++++-- SlackKit/Sources/SlackWebAPI.swift | 38 ++++++++++++------------- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index 8c25c01..1e067d6 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -57,7 +57,7 @@ public class Client: WebSocketDelegate { self.token = token } - public var slackWebAPI: SlackWebAPI? + public var webAPI: SlackWebAPI? internal var webSocket: WebSocket? private var dispatcher: EventDispatcher? @@ -68,8 +68,8 @@ public class Client: WebSocketDelegate { public func connect() { dispatcher = EventDispatcher(client: self) - slackWebAPI = SlackWebAPI(client: self) - slackWebAPI?.rtmStart(success: { + webAPI = SlackWebAPI(client: self) + webAPI?.rtmStart(success: { (response) -> Void in self.initialSetup(response) if let socketURL = response["url"] as? String { diff --git a/SlackKit/Sources/ClientExtensions.swift b/SlackKit/Sources/ClientExtensions.swift index 60bfa99..8e1a70d 100644 --- a/SlackKit/Sources/ClientExtensions.swift +++ b/SlackKit/Sources/ClientExtensions.swift @@ -49,7 +49,7 @@ extension Client { if let channel = channel { success(imID: channel.0) } else { - slackWebAPI?.openIM(id, success: success, failure: failure) + webAPI?.openIM(id, success: success, failure: failure) } } @@ -65,7 +65,7 @@ extension Client { } -extension String { +internal extension String { func slackFormatEscaping() -> String { var escapedString = stringByReplacingOccurrencesOfString("&", withString: "&") @@ -73,4 +73,13 @@ extension String { escapedString = stringByReplacingOccurrencesOfString(">", withString: ">") return escapedString } -} \ No newline at end of file + +} + +public extension NSDate { + + func slackTimestamp() -> String { + return NSNumber(double: timeIntervalSince1970).stringValue + } + +} diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index ae9c030..57816a8 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -152,10 +152,10 @@ public class SlackWebAPI { } } - public func markChannel(channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { + public func markChannel(channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) { mark(.ChannelsMark, channel: channel, timestamp: timestamp, success: { - (marked) -> Void in - success?(marked:marked) + (ts) -> Void in + success?(ts:timestamp) }) {(error) -> Void in failure?(error: error) } @@ -277,10 +277,10 @@ public class SlackWebAPI { } } - public func markGroup(channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { + public func markGroup(channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) { mark(.GroupsMark, channel: channel, timestamp: timestamp, success: { - (marked) -> Void in - success?(marked: marked) + (ts) -> Void in + success?(ts: timestamp) }) {(error) -> Void in failure?(error: error) } @@ -342,10 +342,10 @@ public class SlackWebAPI { } } - public func markIM(channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { + public func markIM(channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) { mark(.IMMark, channel: channel, timestamp: timestamp, success: { - (marked) -> Void in - success?(marked: marked) + (ts) -> Void in + success?(ts: timestamp) }) {(error) -> Void in failure?(error: error) } @@ -390,21 +390,21 @@ public class SlackWebAPI { } } - public func markMPIM(channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { + public func markMPIM(channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) { mark(.MPIMMark, channel: channel, timestamp: timestamp, success: { - (marked) -> Void in - success?(marked: marked) + (ts) -> Void in + success?(ts: timestamp) }) {(error) -> Void in failure?(error: error) } } - public func openMPIM(userIDs: [String], success: ((imID: String?)->Void)?, failure: FailureClosure?) { + public func openMPIM(userIDs: [String], success: ((mpimID: String?)->Void)?, failure: FailureClosure?) { let parameters = ["users":userIDs.joinWithSeparator(",")] client.api.request(.MPIMOpen, token: client.token, parameters: parameters, successClosure: { (response) -> Void in let group = response["group"] as? [String: AnyObject] - success?(imID: group?["id"] as? String) + success?(mpimID: group?["id"] as? String) }) {(error) -> Void in failure?(error: error) } @@ -452,7 +452,7 @@ public class SlackWebAPI { // One of file, file_comment, or the combination of channel and timestamp must be specified. public func removeReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((unreacted: Bool)->Void)?, failure: FailureClosure?) { - react(.ReactionsAdd, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { + react(.ReactionsRemove, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { (ok) -> Void in success?(unreacted: ok) }) {(error) -> Void in @@ -473,7 +473,7 @@ public class SlackWebAPI { //MARK: - Stars // One of file, file_comment, channel, or the combination of channel and timestamp must be specified. public func addStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((starred: Bool)->Void)?, failure: FailureClosure?) { - star(.StarsRemove, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { + star(.StarsAdd, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: { (ok) -> Void in success?(starred: ok) }) {(error) -> Void in @@ -533,7 +533,7 @@ public class SlackWebAPI { } } - public func userList(includePresence: Bool = false, success: ((userList: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { + public func usersList(includePresence: Bool = false, success: ((userList: [[String: AnyObject]]?)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["presence":includePresence] client.api.request(.UsersList, token: client.token, parameters: parameters, successClosure: { (response) -> Void in @@ -603,11 +603,11 @@ public class SlackWebAPI { } } - private func mark(endpoint: SlackAPIEndpoint, channel: String, timestamp: String, success: ((marked: Bool)->Void)?, failure: FailureClosure?) { + private func mark(endpoint: SlackAPIEndpoint, channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["channel": channel, "ts": timestamp] client.api.request(endpoint, token: client.token, parameters: parameters, successClosure: { (response) -> Void in - success?(marked: true) + success?(ts: timestamp) }) {(error) -> Void in failure?(error: error) } From d8c8c3cb574d3f8c7cc1d593b2592981b2d713d9 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Thu, 18 Feb 2016 13:24:14 -0500 Subject: [PATCH 25/26] Updates --- README.md | 95 ++++++++++++++++++++----- SlackKit/Sources/Client.swift | 11 +-- SlackKit/Sources/ClientExtensions.swift | 2 +- 3 files changed, 85 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 5e40fd1..2272470 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ ![SlackKit](https://cloud.githubusercontent.com/assets/8311605/10260893/5ec60f96-694e-11e5-91fd-da6845942201.png) ##iOS/OS X Slack Client Library ###Description -This is a Slack client library for iOS and OS X written in Swift. It's intended to expose all of the functionality of Slack's [Real Time Messaging API](https://api.slack.com/rtm). +This is a Slack client library for iOS and OS X written in Swift. It's intended to expose all of the functionality of Slack's [Real Time Messaging API](https://api.slack.com/rtm) as well as the [web APIs](https://api.slack.com/web) that are accessible by [bot users](https://api.slack.com/bot-users). ###Installation -####Swift Package Manager (Swift 2.2 and up) +####Swift Package Manager Add SlackKit to your Package.swift ```swift @@ -37,17 +37,88 @@ import SlackKit ###Usage To use SlackKit you'll need a bearer token which identifies a single user. You can generate a [full access token or create one using OAuth 2](https://api.slack.com/web). -Once you have a token, give it to the Client: +Once you have a token, initialize a client instance using it: ```swift -Client.sharedInstance.setAuthToken("YOUR_SLACK_AUTH_TOKEN") +let client = Client(apiToken: "YOUR_SLACK_API_TOKEN") + ``` -and connect: + +If you want to receive messages from the Slack RTM API, connect to it. ```swift -Client.sharedInstance.connect() +client.connect() ``` + Once connected, the client will begin to consume any messages sent by the Slack RTM API. +####Web API Methods +SlackKit currently supports the a subset of the Slack Web APIs that is available to bot users: + +- api.test +- auth.test +- channels.history +- channels.info +- channels.list +- channels.mark +- channels.setPurpose +- channels.setTopic +- chat.delete +- chat.postMessage +- chat.update +- emoji.list +- files.delete +- files.upload +- groups.close +- groups.history +- groups.info +- groups.list +- groups.mark +- groups.open +- groups.setPurpose +- groups.setTopic +- im.close +- im.history +- im.list +- im.mark +- im.open +- mpim.close +- mpim.history +- mpim.list +- mpim.mark +- mpim.open +- pins.add +- pins.list +- pins.remove +- reactions.add +- reactions.get +- reactions.list +- reactions.remove +- rtm.start +- stars.add +- stars.remove +- team.info +- users.getPresence +- users.info +- users.list +- users.setActive +- users.setPresence + +They can be accessed through a Client object’s `webAPI` property: +```swift +client.webAPI.authenticationTest({ +(authenticated) -> Void in + print(authenticated) + }){(error) -> Void in + print(error) +} +``` + ####Delegate methods + +To receive delegate callbacks for certain events, register an object as the delegate for those events: +```swift +client.slackEventsDelegate = self +``` + There are a number of delegates that you can set to receive callbacks for certain events. #####SlackEventsDelegate @@ -138,18 +209,6 @@ func subteamSelfAdded(subteamID: String) func subteamSelfRemoved(subteamID: String) ``` -###Examples -####Sending a Message: -```swift -Client.sharedInstance.sendMessage(message: "Hello, world!", channelID: "CHANNEL_ID") -``` - -####Print a List of Users in a Channel: -```swift -let users = Client.sharedInstance.channels?["CHANNEL_ID"]?.members -print(users) -``` - ###Get In Touch [@pvzig](https://twitter.com/pvzig) diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index 1e067d6..1850f4c 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -57,19 +57,22 @@ public class Client: WebSocketDelegate { self.token = token } - public var webAPI: SlackWebAPI? + public var webAPI: SlackWebAPI { + return SlackWebAPI(client: self) + } internal var webSocket: WebSocket? private var dispatcher: EventDispatcher? internal let api = NetworkInterface() - required public init() {} + required public init(apiToken: String) { + self.token = apiToken + } public func connect() { dispatcher = EventDispatcher(client: self) - webAPI = SlackWebAPI(client: self) - webAPI?.rtmStart(success: { + webAPI.rtmStart(success: { (response) -> Void in self.initialSetup(response) if let socketURL = response["url"] as? String { diff --git a/SlackKit/Sources/ClientExtensions.swift b/SlackKit/Sources/ClientExtensions.swift index 8e1a70d..8e8cc05 100644 --- a/SlackKit/Sources/ClientExtensions.swift +++ b/SlackKit/Sources/ClientExtensions.swift @@ -49,7 +49,7 @@ extension Client { if let channel = channel { success(imID: channel.0) } else { - webAPI?.openIM(id, success: success, failure: failure) + webAPI.openIM(id, success: success, failure: failure) } } From 572e363717fecbbc1bfb052ad5a94df689007189 Mon Sep 17 00:00:00 2001 From: Peter Zignego Date: Thu, 18 Feb 2016 13:27:24 -0500 Subject: [PATCH 26/26] Typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2272470..08b2d2c 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ client.connect() Once connected, the client will begin to consume any messages sent by the Slack RTM API. ####Web API Methods -SlackKit currently supports the a subset of the Slack Web APIs that is available to bot users: +SlackKit currently supports the a subset of the Slack Web APIs that are available to bot users: - api.test - auth.test