// // WebAPI.swift // // Copyright © 2017 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. //swiftlint:disable file_length import Foundation #if !COCOAPODS @_exported import SKCore #endif public final class WebAPI { public typealias SuccessClosure = (_ success: Bool) -> Void public typealias FailureClosure = (_ error: SlackError) -> Void public typealias CommentClosure = (_ comment: Comment) -> Void public typealias ChannelClosure = (_ channel: Channel) -> Void public typealias ChannelsClosure = (_ channels: [Channel], _ nextCursor: String?) -> Void public typealias MessageClosure = (_ message: Message) -> Void public typealias HistoryClosure = (_ history: History) -> Void public typealias FileClosure = (_ file: File) -> Void public typealias ItemsClosure = (_ items: [Item]?) -> Void public typealias AuthTestClosure = (_ user: String?, _ team: String?) -> Void public enum InfoType: String { case purpose, topic } public enum ParseMode: String { case full, none } public enum Presence: String { case auto, away } fileprivate enum ChannelType: String { case channel, group, im } public enum ConversationType: String { case public_channel, private_channel, mpim, im } fileprivate let networkInterface: NetworkInterface fileprivate let token: String public init(token: String) { self.networkInterface = NetworkInterface() self.token = token } } // MARK: - RTM extension WebAPI { public static func rtmConnect( token: String, batchPresenceAware: Bool = false, presenceSub: Bool = false, success: ((_ response: [String: Any]) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "batch_presence_aware": batchPresenceAware, "presence_sub": presenceSub ] NetworkInterface().request(.rtmConnect, accessToken: token, parameters: parameters, successClosure: {(response) in success?(response) }) {(error) in failure?(error) } } } // MARK: - Auth extension WebAPI { public func authenticationTest(success: AuthTestClosure?, failure: FailureClosure?) { networkInterface.request(.authTest, accessToken: token, parameters: [:], successClosure: { (response) in success?(response["user_id"] as? String, response["team_id"] as? String) }) {(error) in failure?(error) } } public static func oauthAccess(clientID: String, clientSecret: String, code: String, redirectURI: String? = nil) -> [String: Any]? { let parameters: [String: Any?] = ["client_id": clientID, "client_secret": clientSecret, "code": code, "redirect_uri": redirectURI] return NetworkInterface().synchronusRequest(.oauthAccess, parameters: parameters) } public static func oauthRevoke( token: String, test: Bool? = nil, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any?] = ["test": test] NetworkInterface().request(.authRevoke, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } } // MARK: - Channels extension WebAPI { public func channelHistory( id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: HistoryClosure?, failure: FailureClosure? ) { history(.channelsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {(history) in success?(history) }) {(error) in failure?(error) } } public func channelsLeave(_ channel: String, success: SuccessClosure?, failure: FailureClosure?) { leave(.channelsLeave, channel: channel, success: success, failure: failure) } public func channelsUnarchive(_ channel: String, success: SuccessClosure?, failure: FailureClosure?) { unarchive(.channelsUnarchive, channel: channel, success: success, failure: failure) } public func channelsRename(_ channel: String, name: String, validate: Bool, success: ChannelClosure?, failure: FailureClosure?) { rename(.channelsRename, channel: channel, name: name, validate: validate, success: success, failure: failure) } public func channelsKick(_ channel: String, user: String, success: SuccessClosure?, failure: FailureClosure?) { kick(.channelsKick, channel: channel, user: user, success: success, failure: failure) } public func setChannelPurpose(channel: String, purpose: String, success: SuccessClosure?, failure: FailureClosure?) { setInfo(.channelsSetPurpose, type: .purpose, channel: channel, text: purpose, success: {(purposeSet) in success?(purposeSet) }) {(error) in failure?(error) } } public func setChannelTopic(channel: String, topic: String, success: SuccessClosure?, failure: FailureClosure?) { setInfo(.channelsSetTopic, type: .topic, channel: channel, text: topic, success: {(topicSet) in success?(topicSet) }) {(error) in failure?(error) } } } // MARK: - Messaging extension WebAPI { public func deleteMessage(channel: String, ts: String, success: SuccessClosure?, failure: FailureClosure?) { let parameters: [String: Any] = ["channel": channel, "ts": ts] networkInterface.request(.chatDelete, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } public func sendMessage( channel: String, text: String, username: String? = nil, asUser: Bool? = nil, parse: ParseMode? = nil, linkNames: Bool? = nil, attachments: [Attachment?]? = nil, blocks: [Block]? = nil, unfurlLinks: Bool? = nil, unfurlMedia: Bool? = nil, iconURL: String? = nil, iconEmoji: String? = nil, success: (((ts: String?, channel: String?)) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "channel": channel, "text": text, "as_user": asUser, "parse": parse?.rawValue, "link_names": linkNames, "unfurl_links": unfurlLinks, "unfurl_media": unfurlMedia, "username": username, "icon_url": iconURL, "icon_emoji": iconEmoji, "attachments": encodeAttachments(attachments), "blocks": encodeBlocks(blocks) ] networkInterface.request(.chatPostMessage, accessToken: token, parameters: parameters, successClosure: {(response) in success?((ts: response["ts"] as? String, response["channel"] as? String)) }) {(error) in failure?(error) } } public func sendThreadedMessage( channel: String, thread: String, text: String, broadcastReply: Bool = false, username: String? = nil, asUser: Bool? = nil, parse: ParseMode? = nil, linkNames: Bool? = nil, attachments: [Attachment?]? = nil, unfurlLinks: Bool? = nil, unfurlMedia: Bool? = nil, iconURL: String? = nil, iconEmoji: String? = nil, success: (((ts: String?, channel: String?)) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "channel": channel, "thread_ts": thread, "text": text, "reply_broadcast": broadcastReply, "as_user": asUser, "parse": parse?.rawValue, "link_names": linkNames, "unfurl_links": unfurlLinks, "unfurl_media": unfurlMedia, "username": username, "icon_url": iconURL, "icon_emoji": iconEmoji, "attachments": encodeAttachments(attachments) ] networkInterface.request(.chatPostMessage, accessToken: token, parameters: parameters, successClosure: {(response) in success?((ts: response["ts"] as? String, response["channel"] as? String)) }) {(error) in failure?(error) } } public func sendEphemeral( channel: String, text: String, user: String, thread: String? = nil, asUser: Bool? = nil, attachments: [Attachment?]? = nil, blocks: [Block]? = nil, linkNames: Bool? = nil, parse: ParseMode? = nil, success: (((ts: String?, channel: String?)) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "channel": channel, "text": text, "user": user, "thread_ts": thread, "as_user": asUser, "attachments": encodeAttachments(attachments), "blocks": encodeBlocks(blocks), "link_names": linkNames, "parse": parse?.rawValue, ] networkInterface.request(.chatPostEphemeral, accessToken: token, parameters: parameters, successClosure: {(response) in success?((ts: response["message_ts"] as? String, response["channel"] as? String)) }) {(error) in failure?(error) } } public func sendMeMessage( channel: String, text: String, success: (((ts: String?, channel: String?)) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any?] = ["channel": channel, "text": text] networkInterface.request(.chatMeMessage, accessToken: token, parameters: parameters, successClosure: {(response) in success?((ts: response["ts"] as? String, response["channel"] as? String)) }) {(error) in failure?(error) } } public func updateMessage( channel: String, ts: String, message: String, attachments: [Attachment?]? = nil, parse: ParseMode = .none, linkNames: Bool = false, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "channel": channel, "ts": ts, "text": message, "parse": parse.rawValue, "link_names": linkNames, "attachments": encodeAttachments(attachments) ] networkInterface.request(.chatUpdate, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } } // MARK: - Do Not Disturb extension WebAPI { public func dndInfo(user: String? = nil, success: ((_ status: DoNotDisturbStatus) -> Void)?, failure: FailureClosure?) { let parameters: [String: Any?] = ["user": user] networkInterface.request(.dndInfo, accessToken: token, parameters: parameters, successClosure: {(response) in success?(DoNotDisturbStatus(status: response)) }) {(error) in failure?(error) } } public func dndTeamInfo( users: [String]? = nil, success: ((_ statuses: [String: DoNotDisturbStatus]) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any?] = ["users": users?.joined(separator: ",")] networkInterface.request(.dndTeamInfo, accessToken: token, parameters: parameters, successClosure: {(response) in guard let usersDictionary = response["users"] as? [String: Any] else { success?([:]) return } success?(self.enumerateDNDStatuses(usersDictionary)) }) {(error) in failure?(error) } } } // MARK: - Emoji extension WebAPI { public func emojiList(success: ((_ emojiList: [String: Any]?) -> Void)?, failure: FailureClosure?) { networkInterface.request(.emojiList, accessToken: token, parameters: [:], successClosure: {(response) in success?(response["emoji"] as? [String: Any]) }) {(error) in failure?(error) } } } // MARK: - Files extension WebAPI { public func deleteFile(fileID: String, success: SuccessClosure?, failure: FailureClosure?) { let parameters = ["file": fileID] networkInterface.request(.filesDelete, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } public func fileInfo( fileID: String, count: Int = 100, page: Int = 1, success: FileClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["file": fileID, "count": count, "page": page] networkInterface.request(.filesInfo, accessToken: token, parameters: parameters, successClosure: {(response) in var file = File(file: response["file"] as? [String: Any]) (response["comments"] as? [[String: Any]])?.forEach { comment in let comment = Comment(comment: comment) if let id = comment.id { file.comments[id] = comment } } success?(file) }) {(error) in failure?(error) } } public func uploadFile( file: Data, filename: String, filetype: String = "auto", title: String? = nil, initialComment: String? = nil, channels: [String]? = nil, ts: String? = nil, success: FileClosure?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "filename": filename, "filetype": filetype, "title": title, "initial_comment": initialComment, "channels": channels?.joined(separator: ","), "thread_ts": ts ] networkInterface.uploadRequest(data: file, accessToken: token, parameters: parameters, successClosure: {(response) in success?(File(file: response["file"] as? [String: Any])) }) {(error) in failure?(error) } } } // MARK: - File Comments extension WebAPI { public func addFileComment(fileID: String, comment: String, success: CommentClosure?, failure: FailureClosure?) { let parameters: [String: Any] = ["file": fileID, "comment": comment] networkInterface.request(.filesCommentsAdd, accessToken: token, parameters: parameters, successClosure: {(response) in success?(Comment(comment: response["comment"] as? [String: Any])) }) {(error) in failure?(error) } } public func editFileComment(fileID: String, commentID: String, comment: String, success: CommentClosure?, failure: FailureClosure?) { let parameters: [String: Any] = ["file": fileID, "id": commentID, "comment": comment] networkInterface.request(.filesCommentsEdit, accessToken: token, parameters: parameters, successClosure: {(response) in success?(Comment(comment: response["comment"] as? [String: Any])) }) {(error) in failure?(error) } } public func deleteFileComment(fileID: String, commentID: String, success: SuccessClosure?, failure: FailureClosure?) { let parameters: [String: Any] = ["file": fileID, "id": commentID] networkInterface.request(.filesCommentsDelete, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } } // MARK: - Groups extension WebAPI { public func closeGroup(groupID: String, success: SuccessClosure?, failure: FailureClosure?) { close(.groupsClose, channelID: groupID, success: {(closed) in success?(closed) }) {(error) in failure?(error) } } public func groupHistory( id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: HistoryClosure?, failure: FailureClosure? ) { history(.groupsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {(history) in success?(history) }) {(error) in failure?(error) } } public func groupInfo(id: String, success: ChannelClosure?, failure: FailureClosure?) { info(.groupsInfo, type:.group, id: id, success: {(channel) in success?(channel) }) {(error) in failure?(error) } } public func groupsList( excludeArchived: Bool = false, excludeMembers: Bool = false, success: ((_ channels: [[String: Any]]?) -> Void)?, failure: FailureClosure? ) { list(.groupsList, type:.group, excludeArchived: excludeArchived, excludeMembers: excludeMembers, success: {(channels) in success?(channels) }) {(error) in failure?(error) } } public func markGroup(channel: String, timestamp: String, success: ((_ ts: String) -> Void)?, failure: FailureClosure?) { mark(.groupsMark, channel: channel, timestamp: timestamp, success: {(ts) in success?(ts) }) {(error) in failure?(error) } } public func openGroup(channel: String, success: SuccessClosure?, failure: FailureClosure?) { let parameters = ["channel": channel] networkInterface.request(.groupsOpen, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } public func setGroupPurpose(channel: String, purpose: String, success: SuccessClosure?, failure: FailureClosure?) { setInfo(.groupsSetPurpose, type: .purpose, channel: channel, text: purpose, success: {(purposeSet) in success?(purposeSet) }) {(error) in failure?(error) } } public func setGroupTopic(channel: String, topic: String, success: SuccessClosure?, failure: FailureClosure?) { setInfo(.groupsSetTopic, type: .topic, channel: channel, text: topic, success: {(topicSet) in success?(topicSet) }) {(error) in failure?(error) } } } // MARK: - IM extension WebAPI { public func closeIM(channel: String, success: SuccessClosure?, failure: FailureClosure?) { close(.imClose, channelID: channel, success: {(closed) in success?(closed) }) {(error) in failure?(error) } } public func imHistory( id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: HistoryClosure?, failure: FailureClosure? ) { history(.imHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {(history) in success?(history) }) {(error) in failure?(error) } } public func imsList( excludeArchived: Bool = false, excludeMembers: Bool = false, success: ((_ channels: [[String: Any]]?) -> Void)?, failure: FailureClosure? ) { list(.imList, type:.im, excludeArchived: excludeArchived, excludeMembers: excludeMembers, success: {(channels) in success?(channels) }) {(error) in failure?(error) } } public func markIM(channel: String, timestamp: String, success: ((_ ts: String) -> Void)?, failure: FailureClosure?) { mark(.imMark, channel: channel, timestamp: timestamp, success: {(ts) in success?(ts) }) {(error) in failure?(error) } } } // MARK: - MPIM extension WebAPI { public func closeMPIM(channel: String, success: SuccessClosure?, failure: FailureClosure?) { close(.mpimClose, channelID: channel, success: {(closed) in success?(closed) }) {(error) in failure?(error) } } public func mpimHistory( id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: HistoryClosure?, failure: FailureClosure? ) { history(.mpimHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {(history) in success?(history) }) {(error) in failure?(error) } } public func mpimsList( excludeArchived: Bool = false, excludeMembers: Bool = false, success: ((_ channels: [[String: Any]]?) -> Void)?, failure: FailureClosure? ) { list(.mpimList, type:.group, excludeArchived: excludeArchived, excludeMembers: excludeMembers, success: {(channels) in success?(channels) }) {(error) in failure?(error) } } public func markMPIM(channel: String, timestamp: String, success: ((_ ts: String) -> Void)?, failure: FailureClosure?) { mark(.mpimMark, channel: channel, timestamp: timestamp, success: {(ts) in success?(ts) }) {(error) in failure?(error) } } public func openMPIM(userIDs: [String], success: ((_ mpimID: String?) -> Void)?, failure: FailureClosure?) { let parameters = ["users": userIDs.joined(separator: ",")] networkInterface.request(.mpimOpen, accessToken: token, parameters: parameters, successClosure: {(response) in let group = response["group"] as? [String: Any] success?(group?["id"] as? String) }) {(error) in failure?(error) } } } // MARK: - Pins extension WebAPI { public func pinsList( channel: String, success: ItemsClosure?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "channel": channel ] networkInterface.request(.pinsList, accessToken: token, parameters: parameters, successClosure: { response in let items = response["items"] as? [[String: Any]] success?(items?.map({ Item(item: $0) })) }) {(error) in failure?(error) } } public func pinItem( channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: SuccessClosure?, failure: FailureClosure? ) { pin(.pinsAdd, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: {(ok) in success?(ok) }) {(error) in failure?(error) } } public func unpinItem( channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: SuccessClosure?, failure: FailureClosure? ) { pin(.pinsRemove, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: {(ok) in success?(ok) }) {(error) in failure?(error) } } private func pin( _ endpoint: Endpoint, channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "channel": channel, "file": file, "file_comment": fileComment, "timestamp": timestamp ] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } } // MARK: - Reactions extension WebAPI { public func addReactionToMessage(name: String, channel: String, timestamp: String, success: SuccessClosure?, failure: FailureClosure?) { addReaction(name: name, channel: channel, timestamp: timestamp, success: success, failure: failure) } public func addReactionToFile(name: String, file: String, success: SuccessClosure?, failure: FailureClosure?) { addReaction(name: name, file: file, success: success, failure: failure) } public func addReactionToFileComment(name: String, fileComment: String, success: SuccessClosure?, failure: FailureClosure?) { addReaction(name: name, fileComment: fileComment, success: success, failure: failure) } private func addReaction( name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: SuccessClosure?, failure: FailureClosure? ) { react(.reactionsAdd, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {(ok) in success?(ok) }) {(error) in failure?(error) } } public func removeReactionFromMessage( name: String, channel: String, timestamp: String, success: SuccessClosure?, failure: FailureClosure? ) { removeReaction(name: name, channel: channel, timestamp: timestamp, success: success, failure: failure) } public func removeReactionFromFile(name: String, file: String, success: SuccessClosure?, failure: FailureClosure?) { removeReaction(name: name, file: file, success: success, failure: failure) } public func removeReactionFromFileComment(name: String, fileComment: String, success: SuccessClosure?, failure: FailureClosure?) { removeReaction(name: name, fileComment: fileComment, success: success, failure: failure) } private func removeReaction( name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: SuccessClosure?, failure: FailureClosure? ) { react(.reactionsRemove, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {(ok) in success?(ok) }) {(error) in failure?(error) } } private func react( _ endpoint: Endpoint, name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "name": name, "file": file, "file_comment": fileComment, "channel": channel, "timestamp": timestamp ] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } private enum ReactionItemType: String { case file, comment, message } public func getReactionsForFile(_ file: String, full: Bool = true, reactions: (([Reaction]) -> Void)?, failure: FailureClosure?) { getReactionsForItem(file, full: full, type: .file, reactions: reactions, failure: failure) } public func getReactionsForComment(_ comment: String, full: Bool = true, reactions: (([Reaction]) -> Void)?, failure: FailureClosure?) { getReactionsForItem(comment: comment, full: full, type: .comment, reactions: reactions, failure: failure) } public func getReactionsForMessage( _ channel: String, timestamp: String, full: Bool = true, reactions: (([Reaction]) -> Void)?, failure: FailureClosure? ) { getReactionsForItem(channel: channel, timestamp: timestamp, full: full, type: .message, reactions: reactions, failure: failure) } private func getReactionsForItem( _ file: String? = nil, comment: String? = nil, channel: String? = nil, timestamp: String? = nil, full: Bool, type: ReactionItemType, reactions: (([Reaction]) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "file": file, "file_comment": comment, "channel": channel, "timestamp": timestamp, "full": full ] networkInterface.request(.reactionsGet, accessToken: token, parameters: parameters, successClosure: {(response) in guard let item = response[type.rawValue] as? [String: Any] else { reactions?([]) return } switch type { case .message: let message = Message(dictionary: item) reactions?(message.reactions) case .file: let file = File(file: item) reactions?(file.reactions) case .comment: let comment = Comment(comment: item) reactions?(comment.reactions) } }) {(error) in failure?(error) } } public func reactionsListForUser( _ user: String? = nil, full: Bool = true, count: Int = 100, page: Int = 1, success: ItemsClosure?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "user": user, "full": full, "count": count, "page": page ] networkInterface.request(.reactionsList, accessToken: token, parameters: parameters, successClosure: {(response) in let items = response["items"] as? [[String: Any]] success?(items?.map({ Item(item: $0) })) }) {(error) in failure?(error) } } } // MARK: - Stars extension WebAPI { public func addStarToChannel(channel: String, success: SuccessClosure?, failure: FailureClosure?) { addStar(channel: channel, success: success, failure: failure) } public func addStarToMessage(channel: String, timestamp: String, success: SuccessClosure?, failure: FailureClosure?) { addStar(channel: channel, timestamp: timestamp, success: success, failure: failure) } public func addStarToFile(file: String, success: SuccessClosure?, failure: FailureClosure?) { addStar(file: file, success: success, failure: failure) } public func addStarToFileComment(fileComment: String, success: SuccessClosure?, failure: FailureClosure?) { addStar(fileComment: fileComment, success: success, failure: failure) } private func addStar( file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: SuccessClosure?, failure: FailureClosure? ) { star(.starsAdd, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {(ok) in success?(ok) }) {(error) in failure?(error) } } public func removeStarFromChannel(channel: String, success: SuccessClosure?, failure: FailureClosure?) { removeStar(channel: channel, success: success, failure: failure) } public func removeStarFromMessage(channel: String, timestamp: String, success: SuccessClosure?, failure: FailureClosure?) { removeStar(channel: channel, timestamp: timestamp, success: success, failure: failure) } public func removeStarFromFile(file: String, success: SuccessClosure?, failure: FailureClosure?) { removeStar(file: file, success: success, failure: failure) } public func removeStarFromFilecomment(fileComment: String, success: SuccessClosure?, failure: FailureClosure?) { removeStar(fileComment: fileComment, success: success, failure: failure) } private func removeStar( file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: SuccessClosure?, failure: FailureClosure? ) { star(.starsRemove, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {(ok) in success?(ok) }) {(error) in failure?(error) } } private func star( _ endpoint: Endpoint, file: String?, fileComment: String?, channel: String?, timestamp: String?, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "file": file, "file_comment": fileComment, "channel": channel, "timestamp": timestamp ] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } } // MARK: - Team extension WebAPI { public func teamInfo(success: ((_ info: [String: Any]?) -> Void)?, failure: FailureClosure?) { networkInterface.request(.teamInfo, accessToken: token, parameters: [:], successClosure: {(response) in success?(response["team"] as? [String: Any]) }) {(error) in failure?(error) } } } // MARK: - Users extension WebAPI { public func userConversations( cursor: String? = nil, excludeArchived: Bool? = nil, limit: Int? = nil, types: [ConversationType]? = nil, userID: String? = nil, success: ChannelsClosure?, failure: FailureClosure? ) { let parameters: [String: Any?] = [ "cursor": cursor, "exclude_archived": excludeArchived, "limit": limit, "types": types?.map({ $0.rawValue }).joined(separator: ","), "user": userID ] networkInterface.request(.usersConversations, accessToken: token, parameters: parameters, successClosure: {(response) in let channels: [Channel] = (response["channels"] as? [[String: Any]])?.map{Channel(channel: $0)} ?? [] success?(channels, (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String) }) {(error) in failure?(error) } } public func userPresence(user: String, success: ((_ presence: String?) -> Void)?, failure: FailureClosure?) { let parameters: [String: Any] = ["user": user] networkInterface.request(.usersGetPresence, accessToken: token, parameters: parameters, successClosure: {(response) in success?(response["presence"] as? String) }) {(error) in failure?(error) } } public func userInfo(id: String, success: ((_ user: User) -> Void)?, failure: FailureClosure?) { let parameters: [String: Any] = ["user": id] networkInterface.request(.usersInfo, accessToken: token, parameters: parameters, successClosure: {(response) in success?(User(user: response["user"] as? [String: Any])) }) {(error) in failure?(error) } } public func usersList(cursor: String? = nil, limit: Int? = nil, includePresence: Bool = false, success: ((_ userList: [[String: Any]]?, _ nextCursor: String?) -> Void)?, failure: FailureClosure?) { var parameters: [String: Any] = ["presence": includePresence] if let cursor = cursor { parameters["cursor"] = cursor } if let limit = limit { parameters["limit"] = limit } networkInterface.request(.usersList, accessToken: token, parameters: parameters, successClosure: {(response) in success?(response["members"] as? [[String: Any]], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String) }) {(error) in failure?(error) } } public func usersLookupByEmail(_ email: String, success: ((_ user: User) -> Void)?, failure: FailureClosure?) { let parameters: [String: Any] = ["email": email] networkInterface.request(.usersLookupByEmail, accessToken: token, parameters: parameters, successClosure: { response in success?(User(user: response["user"] as? [String: Any])) }) { error in failure?(error) } } public func usersProfileSet(profile: User.Profile, success: SuccessClosure?, failure: FailureClosure?) { let profileValues = ([ "first_name": profile.firstName, "last_name": profile.lastName, "real_name": profile.realName, "email": profile.email, "phone": profile.phone, "status_text": profile.statusText, "status_emoji": profile.statusEmoji, "status_expiration": profile.statusExpiration ] as [String: Any?]) .filter { $0.value != nil } .mapValues { $0! } do { let data = try JSONSerialization.data(withJSONObject: ["profile": profileValues]) let urlComponents = URLComponents(string: "https://slack.com/api/users.profile.set") guard let requestString = urlComponents?.url?.absoluteString else { throw SlackError.clientNetworkError } networkInterface.customRequest(requestString, token: token, data: data, success: { _ in success?(true) }) {(error) in failure?(error) } } catch { failure?(error as? SlackError ?? SlackError.unknownError) } } public func setUserActive(success: SuccessClosure?, failure: FailureClosure?) { networkInterface.request(.usersSetActive, accessToken: token, parameters: [:], successClosure: { _ in success?(true) }) {(error) in failure?(error) } } public func setUserPresence(presence: Presence, success: SuccessClosure?, failure: FailureClosure?) { let parameters: [String: Any] = ["presence": presence.rawValue] networkInterface.request(.usersSetPresence, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } } // MARK: - Conversations extension WebAPI { public func conversationsArchive(channel: String, success: (() -> Void)?, failure: FailureClosure?) { let parameters = ["channel": channel] networkInterface.request(.conversationsArchive, accessToken: token, parameters: parameters, successClosure: {_ in }) {(error) in failure?(error) } } public func conversationsCreate( name: String, isPrivate: Bool = false, success: ((_ id: String?, _ name: String?, _ creator: String?) -> Void)?, failure: FailureClosure? ) { let parameters = [ "name": name, "is_private": isPrivate ] as [String : Any] networkInterface.request(.conversationsOpen, accessToken: token, parameters: parameters, successClosure: {(response) in let group = response["channel"] as? [String: Any] success?( group?["id"] as? String, group?["name"] as? String, group?["creator"] as? String ) }) {(error) in failure?(error) } } public func conversationsList( excludeArchived: Bool = false, cursor: String? = nil, limit: Int? = nil, types: [ConversationType]? = nil, success: ((_ channels: [[String: Any]]?, _ nextCursor: String?) -> Void)?, failure: FailureClosure? ) { var parameters: [String: Any] = ["exclude_archived": excludeArchived] if let cursor = cursor { parameters["cursor"] = cursor } if let limit = limit { parameters["limit"] = limit } if let types = types { parameters["types"] = types.map({ $0.rawValue }).joined(separator: ",") } networkInterface.request(.conversationsList, accessToken: token, parameters: parameters, successClosure: {(response) in success?(response["channels"] as? [[String: Any]], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String) }) {(error) in failure?(error) } } public func conversationsReplies( id: String, ts: String, cursor: String? = nil, inclusive: Bool = false, latest: String = "\(Date().timeIntervalSince1970)", limit: Int = 10, oldest: String = "0", success: ((_ channels: [[String: Any]]?, _ nextCursor: String?) -> Void)?, failure: FailureClosure? ) { var parameters: [String: Any] = [ "channel": id, "ts": ts, "inclusive": inclusive, "limit": limit, "latest": latest, "oldest": oldest, ] if let cursor = cursor { parameters["cursor"] = cursor } networkInterface.request(.conversationsReplies, accessToken: token, parameters: parameters, successClosure: {(response) in success?(response["messages"] as? [[String: Any]], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String) }) {(error) in failure?(error) } } public func conversationsMembers( id: String, cursor: String? = nil, limit: Int? = nil, success: ((_ members: [String]?, _ nextCursor: String?) -> Void)?, failure: FailureClosure? ) { var parameters: [String: Any] = [ "channel": id ] if let cursor = cursor { parameters["cursor"] = cursor } if let limit = limit { parameters["limit"] = limit } networkInterface.request(.conversationsMembers, accessToken: token, parameters: parameters, successClosure: {(response) in success?(response["members"] as? [String], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String) }) {(error) in failure?(error) } } public func conversationsHistory( id: String, cursor: String? = nil, inclusive: Bool = false, latest: String = "\(Date().timeIntervalSince1970)", limit: Int = 10, oldest: String = "0", success: ((_ channels: [[String: Any]]?, _ nextCursor: String?) -> Void)?, failure: FailureClosure? ) { var parameters: [String: Any] = [ "channel": id, "inclusive": inclusive, "limit": limit, "latest": latest, "oldest": oldest, ] if let cursor = cursor { parameters["cursor"] = cursor } networkInterface.request(.conversationsHistory, accessToken: token, parameters: parameters, successClosure: {(response) in success?(response["messages"] as? [[String: Any]], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String) }) {(error) in failure?(error) } } public func conversationsOpen( userIDs: [String], success: ((_ imID: String?) -> Void)?, failure: FailureClosure? ) { let parameters = [ "users": userIDs.joined(separator: ",") ] networkInterface.request(.conversationsOpen, accessToken: token, parameters: parameters, successClosure: {(response) in let group = response["channel"] as? [String: Any] success?(group?["id"] as? String) }) {(error) in failure?(error) } } } // MARK: - Search extension WebAPI { public enum SearchSort: String { case score case timestamp } public enum SearchSortDirection: String { case asc case desc } public func search( query: String, count: Int = 20, highlight: Bool = false, page: Int = 1, sort: SearchSort = .score, sortDir: SearchSortDirection = .desc, success: ((_ files: [[String : Any]]?, _ messages: [[String : Any]]?) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any] = [ "query": query, "count": count, "highlight": highlight, "page": page, "sort": sort.rawValue, "sort_dir": sortDir.rawValue, ] networkInterface.request(.searchAll, accessToken: token, parameters: parameters, successClosure: { (response) in success?( (response["files"] as? [String : Any])?["matches"] as? [[String : Any]], (response["messages"] as? [String : Any])?["matches"] as? [[String : Any]] ) }) { (error) in failure?(error) } } public func searchFiles( query: String, count: Int = 20, highlight: Bool = false, page: Int = 1, sort: SearchSort = .score, sortDir: SearchSortDirection = .desc, success: ((_ files: [[String : Any]]?) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any] = [ "query": query, "count": count, "highlight": highlight, "page": page, "sort": sort.rawValue, "sort_dir": sortDir.rawValue, ] networkInterface.request(.searchFiles, accessToken: token, parameters: parameters, successClosure: { (response) in success?( (response["files"] as? [String : Any])?["matches"] as? [[String : Any]] ) }) { (error) in failure?(error) } } public func searchMessages( query: String, count: Int = 20, highlight: Bool = false, page: Int = 1, sort: SearchSort = .score, sortDir: SearchSortDirection = .desc, success: ((_ messages: [[String : Any]]?) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any] = [ "query": query, "count": count, "highlight": highlight, "page": page, "sort": sort.rawValue, "sort_dir": sortDir.rawValue, ] networkInterface.request(.searchMessages, accessToken: token, parameters: parameters, successClosure: { (response) in success?( (response["messages"] as? [String : Any])?["matches"] as? [[String : Any]] ) }) { (error) in failure?(error) } } } // MARK: - Utilities extension WebAPI { fileprivate func encodeAttachments(_ attachments: [Attachment?]?) -> String? { if let attachments = attachments { var attachmentArray: [[String: Any]] = [] for attachment in attachments { if let attachment = attachment { attachmentArray.append(attachment.dictionary) } } do { let data = try JSONSerialization.data(withJSONObject: attachmentArray, options: []) return String(data: data, encoding: String.Encoding.utf8) } catch let error { print(error) } } return nil } fileprivate func encodeBlocks(_ blocks: [Block]?) -> String? { if let blocks = blocks { let blocksArray: [[String: Any]] = blocks.map { $0.dictionary } do { let data = try JSONSerialization.data(withJSONObject: blocksArray, options: []) return String(data: data, encoding: String.Encoding.utf8) } catch let error { print(error) } } return nil } fileprivate func enumerateDNDStatuses(_ statuses: [String: Any]) -> [String: DoNotDisturbStatus] { var retVal = [String: DoNotDisturbStatus]() for key in statuses.keys { retVal[key] = DoNotDisturbStatus(status: statuses[key] as? [String: Any]) } return retVal } fileprivate func close(_ endpoint: Endpoint, channelID: String, success: SuccessClosure?, failure: FailureClosure?) { let parameters: [String: Any] = ["channel": channelID] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } fileprivate func history( _ endpoint: Endpoint, id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: HistoryClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = [ "channel": id, "latest": latest, "oldest": oldest, "inclusive": inclusive, "count": count, "unreads": unreads ] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: {(response) in success?(History(history: response)) }) {(error) in failure?(error) } } fileprivate func info( _ endpoint: Endpoint, type: ChannelType, id: String, success: ChannelClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["channel": id] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: {(response) in success?(Channel(channel: response[type.rawValue] as? [String: Any])) }) {(error) in failure?(error) } } fileprivate func list( _ endpoint: Endpoint, type: ChannelType, excludeArchived: Bool = false, excludeMembers: Bool = false, success: ((_ channels: [[String: Any]]?) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any] = ["exclude_archived": excludeArchived, "exclude_members": excludeMembers] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: {(response) in success?(response[type.rawValue+"s"] as? [[String: Any]]) }) {(error) in failure?(error) } } fileprivate func mark( _ endpoint: Endpoint, channel: String, timestamp: String, success: ((_ ts: String) -> Void)?, failure: FailureClosure? ) { let parameters: [String: Any] = ["channel": channel, "ts": timestamp] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in success?(timestamp) }) {(error) in failure?(error) } } fileprivate func setInfo( _ endpoint: Endpoint, type: InfoType, channel: String, text: String, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["channel": channel, type.rawValue: text] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } fileprivate func create( _ endpoint: Endpoint, name: String, success: ChannelClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["name": name] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: {(response) in success?(Channel(channel: response["channel"] as? [String: Any])) }) {(error) in failure?(error) } } fileprivate func invite( _ endpoint: Endpoint, channel: String, user: String, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["channel": channel, "user": user] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in success?(true) }) {(error) in failure?(error) } } fileprivate func join( _ endpoint: Endpoint, name: String, validate: Bool, success: ChannelClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["name": name, "validate": validate] networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { response in success?(Channel(channel: response["channel"] as? [String: Any])) }) {(error) in failure?(error) } } fileprivate func leave( _ endpoint: Endpoint, channel: String, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["channel": channel] networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { _ in success?(true) }) {(error) in failure?(error) } } fileprivate func archive( _ endpoint: Endpoint, channel: String, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["channel": channel] networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { _ in success?(true) }) {(error) in failure?(error) } } fileprivate func unarchive( _ endpoint: Endpoint, channel: String, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["channel": channel] networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { _ in success?(true) }) {(error) in failure?(error) } } fileprivate func rename( _ endpoint: Endpoint, channel: String, name: String, validate: Bool, success: ChannelClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["channel": channel, "name": name, "validate": validate ] networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { response in success?(Channel(channel: response["channel"] as? [String: Any])) }) {(error) in failure?(error) } } fileprivate func kick( _ endpoint: Endpoint, channel: String, user: String, success: SuccessClosure?, failure: FailureClosure? ) { let parameters: [String: Any] = ["channel": channel, "user": user] networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { _ in success?(true) }) {(error) in failure?(error) } } } // MARK: - Deprecated extension WebAPI { // MARK: channels.* @available(*, deprecated, message: "Use conversationsArchive instead.") public func channelsArchive(_ channel: String, success: SuccessClosure?, failure: FailureClosure?) { archive(.channelsArchive, channel: channel, success: success, failure: failure) } @available(*, deprecated, message: "Use conversationsCreate instead.") public func createChannel(channel: String, success: ChannelClosure?, failure: FailureClosure?) { create(.channelsCreate, name: channel, success: success, failure: failure) } @available(*, deprecated) public func channelInfo(id: String, success: ChannelClosure?, failure: FailureClosure?) { info(.channelsInfo, type:.channel, id: id, success: {(channel) in success?(channel) }) {(error) in failure?(error) } } @available(*, deprecated) public func inviteToChannel(_ channel: String, user: String, success: SuccessClosure?, failure: FailureClosure?) { invite(.channelsInvite, channel: channel, user: user, success: success, failure: failure) } @available(*, deprecated) public func channelsJoin(_ name: String, validate: Bool, success: ChannelClosure?, failure: FailureClosure?) { join(.channelsJoin, name: name, validate: validate, success: success, failure: failure) } @available(*, deprecated) public func channelsList( excludeArchived: Bool = false, excludeMembers: Bool = false, success: ((_ channels: [[String: Any]]?) -> Void)?, failure: FailureClosure? ) { list(.channelsList, type:.channel, excludeArchived: excludeArchived, excludeMembers: excludeMembers, success: {(channels) in success?(channels) }) {(error) in failure?(error) } } @available(*, deprecated) public func markChannel(channel: String, timestamp: String, success: ((_ ts: String) -> Void)?, failure: FailureClosure?) { mark(.channelsMark, channel: channel, timestamp: timestamp, success: {(ts) in success?(ts) }) {(error) in failure?(error) } } // MARK: im.* @available(*, deprecated, message: "Use conversationsOpen instead.") public func openIM(userID: String, success: ((_ imID: String?) -> Void)?, failure: FailureClosure?) { let parameters = ["user": userID] networkInterface.request(.imOpen, accessToken: token, parameters: parameters, successClosure: {(response) in let group = response["channel"] as? [String: Any] success?(group?["id"] as? String) }) {(error) in failure?(error) } } }