diff --git a/README.md b/README.md index f98e0a1..4087317 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![SlackKit](https://cloud.githubusercontent.com/assets/8311605/10260893/5ec60f96-694e-11e5-91fd-da6845942201.png) -##iOS/OS X Slack Client Library +##iOS, OS X, and tvOS Slack Client Library ###Description This is a Slack client library for OS X, iOS, and tvOS 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). @@ -32,7 +32,7 @@ carthage bootstrap carthage bootstrap --configuration "Debug" ``` -Drag the built `SlackKit.framework`, `SlackKit_iOS.framework`, or `SlackKit_tvOS.framework` into your Xcode project. +Drag the built `SlackKit.framework` into your Xcode project. ####Swift Package Manager Add SlackKit to your Package.swift diff --git a/SlackKit.podspec b/SlackKit.podspec index b14441d..0ac573f 100644 --- a/SlackKit.podspec +++ b/SlackKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SlackKit" - s.version = "1.0.3" + s.version = "1.1.0" s.summary = "a Slack client library for OS X, iOS, and tvOS written in Swift" s.homepage = "https://github.com/pvzig/SlackKit" s.license = 'MIT' diff --git a/SlackKit.xcodeproj/project.pbxproj b/SlackKit.xcodeproj/project.pbxproj index 60982cd..4ee1a68 100644 --- a/SlackKit.xcodeproj/project.pbxproj +++ b/SlackKit.xcodeproj/project.pbxproj @@ -87,8 +87,8 @@ 260EC2301C4DC61D0093B253 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Extensions.swift; path = Sources/Extensions.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 = ""; }; - 263993B21CE90EE0004A6E93 /* SlackKit_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 263993D11CE90EED004A6E93 /* SlackKit_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 263993B21CE90EE0004A6E93 /* SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 263993D11CE90EED004A6E93 /* 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 = ""; }; 268E46131CE8F79D009F19CC /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-iOS.plist"; path = "Supporting Files/Info-iOS.plist"; sourceTree = ""; }; 268E46141CE8F79D009F19CC /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-tvOS.plist"; path = "Supporting Files/Info-tvOS.plist"; sourceTree = ""; }; @@ -174,8 +174,8 @@ children = ( 26072A341BB48B3A00CD650C /* SlackKit.framework */, 2601D6241C7688610012BF22 /* OSX-Sample.app */, - 263993B21CE90EE0004A6E93 /* SlackKit_iOS.framework */, - 263993D11CE90EED004A6E93 /* SlackKit_tvOS.framework */, + 263993B21CE90EE0004A6E93 /* SlackKit.framework */, + 263993D11CE90EED004A6E93 /* SlackKit.framework */, ); name = Products; sourceTree = ""; @@ -187,8 +187,8 @@ 26BBA1871C398E3C00BF7225 /* Bot.swift */, 26BBA1881C398E3C00BF7225 /* Channel.swift */, 26BBA1891C398E3C00BF7225 /* Client.swift */, - C1A85FF81CE3BCEF00756C40 /* Client+EventHandling.swift */, C1A85FF71CE3BCEF00756C40 /* Client+EventDispatching.swift */, + C1A85FF81CE3BCEF00756C40 /* Client+EventHandling.swift */, C16C98791CE7D3DD00692776 /* Client+Utilities.swift */, 26BBA18A1C398E3C00BF7225 /* Event.swift */, 26BBA18B1C398E3C00BF7225 /* EventDelegate.swift */, @@ -273,9 +273,9 @@ productReference = 2601D6241C7688610012BF22 /* OSX-Sample.app */; productType = "com.apple.product-type.application"; }; - 26072A331BB48B3A00CD650C /* SlackKit */ = { + 26072A331BB48B3A00CD650C /* SlackKit OS X */ = { isa = PBXNativeTarget; - buildConfigurationList = 26072A3C1BB48B3B00CD650C /* Build configuration list for PBXNativeTarget "SlackKit" */; + buildConfigurationList = 26072A3C1BB48B3B00CD650C /* Build configuration list for PBXNativeTarget "SlackKit OS X" */; buildPhases = ( 26072A2F1BB48B3A00CD650C /* Sources */, 26072A301BB48B3A00CD650C /* Frameworks */, @@ -286,14 +286,14 @@ ); dependencies = ( ); - name = SlackKit; + name = "SlackKit OS X"; productName = SlackRTMKit; productReference = 26072A341BB48B3A00CD650C /* SlackKit.framework */; productType = "com.apple.product-type.framework"; }; - 263993951CE90EE0004A6E93 /* SlackKit_iOS */ = { + 263993951CE90EE0004A6E93 /* SlackKit iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = 263993AF1CE90EE0004A6E93 /* Build configuration list for PBXNativeTarget "SlackKit_iOS" */; + buildConfigurationList = 263993AF1CE90EE0004A6E93 /* Build configuration list for PBXNativeTarget "SlackKit iOS" */; buildPhases = ( 263993961CE90EE0004A6E93 /* Sources */, 263993AA1CE90EE0004A6E93 /* Frameworks */, @@ -304,14 +304,14 @@ ); dependencies = ( ); - name = SlackKit_iOS; + name = "SlackKit iOS"; productName = SlackRTMKit; - productReference = 263993B21CE90EE0004A6E93 /* SlackKit_iOS.framework */; + productReference = 263993B21CE90EE0004A6E93 /* SlackKit.framework */; productType = "com.apple.product-type.framework"; }; - 263993B41CE90EED004A6E93 /* SlackKit_tvOS */ = { + 263993B41CE90EED004A6E93 /* SlackKit tvOS */ = { isa = PBXNativeTarget; - buildConfigurationList = 263993CE1CE90EED004A6E93 /* Build configuration list for PBXNativeTarget "SlackKit_tvOS" */; + buildConfigurationList = 263993CE1CE90EED004A6E93 /* Build configuration list for PBXNativeTarget "SlackKit tvOS" */; buildPhases = ( 263993B51CE90EED004A6E93 /* Sources */, 263993C91CE90EED004A6E93 /* Frameworks */, @@ -322,9 +322,9 @@ ); dependencies = ( ); - name = SlackKit_tvOS; + name = "SlackKit tvOS"; productName = SlackRTMKit; - productReference = 263993D11CE90EED004A6E93 /* SlackKit_tvOS.framework */; + productReference = 263993D11CE90EED004A6E93 /* SlackKit.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ @@ -358,9 +358,9 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 26072A331BB48B3A00CD650C /* SlackKit */, - 263993951CE90EE0004A6E93 /* SlackKit_iOS */, - 263993B41CE90EED004A6E93 /* SlackKit_tvOS */, + 26072A331BB48B3A00CD650C /* SlackKit OS X */, + 263993951CE90EE0004A6E93 /* SlackKit iOS */, + 263993B41CE90EED004A6E93 /* SlackKit tvOS */, 2601D6231C7688610012BF22 /* OSX-Sample */, ); }; @@ -682,7 +682,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = SlackKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; @@ -710,7 +710,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = SlackKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; @@ -736,7 +736,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = SlackKit; SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; @@ -764,7 +764,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = SlackKit; SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; @@ -793,7 +793,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 26072A3C1BB48B3B00CD650C /* Build configuration list for PBXNativeTarget "SlackKit" */ = { + 26072A3C1BB48B3B00CD650C /* Build configuration list for PBXNativeTarget "SlackKit OS X" */ = { isa = XCConfigurationList; buildConfigurations = ( 26072A3D1BB48B3B00CD650C /* Debug */, @@ -802,7 +802,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 263993AF1CE90EE0004A6E93 /* Build configuration list for PBXNativeTarget "SlackKit_iOS" */ = { + 263993AF1CE90EE0004A6E93 /* Build configuration list for PBXNativeTarget "SlackKit iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 263993B01CE90EE0004A6E93 /* Debug */, @@ -811,7 +811,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 263993CE1CE90EED004A6E93 /* Build configuration list for PBXNativeTarget "SlackKit_tvOS" */ = { + 263993CE1CE90EED004A6E93 /* Build configuration list for PBXNativeTarget "SlackKit tvOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 263993CF1CE90EED004A6E93 /* Debug */, diff --git a/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit.xcscheme b/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit OS X.xcscheme similarity index 95% rename from SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit.xcscheme rename to SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit OS X.xcscheme index 227cda8..60dc5e0 100644 --- a/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit.xcscheme +++ b/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit OS X.xcscheme @@ -16,7 +16,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "26072A331BB48B3A00CD650C" BuildableName = "SlackKit.framework" - BlueprintName = "SlackKit" + BlueprintName = "SlackKit OS X" ReferencedContainer = "container:SlackKit.xcodeproj"> @@ -47,7 +47,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "26072A331BB48B3A00CD650C" BuildableName = "SlackKit.framework" - BlueprintName = "SlackKit" + BlueprintName = "SlackKit OS X" ReferencedContainer = "container:SlackKit.xcodeproj"> @@ -65,7 +65,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "26072A331BB48B3A00CD650C" BuildableName = "SlackKit.framework" - BlueprintName = "SlackKit" + BlueprintName = "SlackKit OS X" ReferencedContainer = "container:SlackKit.xcodeproj"> diff --git a/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit_iOS.xcscheme b/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit iOS.xcscheme similarity index 89% rename from SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit_iOS.xcscheme rename to SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit iOS.xcscheme index f2dbb6c..5abeb05 100644 --- a/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit_iOS.xcscheme +++ b/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit iOS.xcscheme @@ -15,8 +15,8 @@ @@ -46,8 +46,8 @@ @@ -64,8 +64,8 @@ diff --git a/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit_tvOS.xcscheme b/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit tvOS.xcscheme similarity index 89% rename from SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit_tvOS.xcscheme rename to SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit tvOS.xcscheme index 83d4dc5..5afe877 100644 --- a/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit_tvOS.xcscheme +++ b/SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit tvOS.xcscheme @@ -15,8 +15,8 @@ @@ -46,8 +46,8 @@ @@ -64,8 +64,8 @@ diff --git a/SlackKit/Sources/Attachment.swift b/SlackKit/Sources/Attachment.swift index 7096d3c..32ca41f 100644 --- a/SlackKit/Sources/Attachment.swift +++ b/SlackKit/Sources/Attachment.swift @@ -38,7 +38,7 @@ public struct Attachment { public let imageURL: String? public let thumbURL: String? - internal init?(attachment: [String: AnyObject]?) { + internal init(attachment: [String: AnyObject]?) { fallback = attachment?["fallback"] as? String color = attachment?["color"] as? String pretext = attachment?["pretext"] as? String @@ -50,12 +50,10 @@ public struct Attachment { text = attachment?["text"] as? String imageURL = attachment?["image_url"] as? String thumbURL = attachment?["thumb_url"] as? String - fields = (attachment?["fields"] as? [[String: AnyObject]])?.objectArrayFromDictionaryArray({(field) -> AttachmentField? in - return AttachmentField(field: field) - }) + fields = (attachment?["fields"] as? [[String: AnyObject]])?.map { AttachmentField(field: $0) } } - public init?(fallback: String, title:String, colorHex: String? = nil, pretext: String? = nil, authorName: String? = nil, authorLink: String? = nil, authorIcon: String? = nil, titleLink: String? = nil, text: String? = nil, fields: [AttachmentField]? = nil, imageURL: String? = nil, thumbURL: String? = nil) { + public init(fallback: String, title:String, colorHex: String? = nil, pretext: String? = nil, authorName: String? = nil, authorLink: String? = nil, authorIcon: String? = nil, titleLink: String? = nil, text: String? = nil, fields: [AttachmentField]? = nil, imageURL: String? = nil, thumbURL: String? = nil) { self.fallback = fallback self.color = colorHex self.pretext = pretext @@ -105,7 +103,7 @@ public struct AttachmentField { public let value: String? public let short: Bool? - internal init?(field: [String: AnyObject]?) { + internal init(field: [String: AnyObject]?) { title = field?["title"] as? String value = field?["value"] as? String short = field?["short"] as? Bool diff --git a/SlackKit/Sources/Bot.swift b/SlackKit/Sources/Bot.swift index 6406257..db914b7 100644 --- a/SlackKit/Sources/Bot.swift +++ b/SlackKit/Sources/Bot.swift @@ -27,7 +27,7 @@ public struct Bot { internal(set) public var name: String? internal(set) public var icons: [String: AnyObject]? - internal init?(bot: [String: AnyObject]?) { + internal init(bot: [String: AnyObject]?) { id = bot?["id"] as? String name = bot?["name"] as? String icons = bot?["icons"] as? [String: AnyObject] diff --git a/SlackKit/Sources/Channel.swift b/SlackKit/Sources/Channel.swift index 35b5436..b487500 100644 --- a/SlackKit/Sources/Channel.swift +++ b/SlackKit/Sources/Channel.swift @@ -49,7 +49,7 @@ public struct Channel { internal(set) public var usersTyping = [String]() internal(set) public var messages = [String: Message]() - internal init?(channel: [String: AnyObject]?) { + internal init(channel: [String: AnyObject]?) { id = channel?["id"] as? String name = channel?["name"] as? String created = channel?["created"] as? Int @@ -70,15 +70,15 @@ public struct Channel { unreadCountDisplay = channel?["unread_count_display"] as? Int hasPins = channel?["has_pins"] as? Bool members = channel?["members"] as? [String] - - if (Message(message: channel?["latest"] as? [String: AnyObject])?.ts == nil) { - latest = Message(ts: channel?["latest"] as? String) + + if let latestMsgDictionary = channel?["latest"] as? [String: AnyObject] { + latest = Message(message: latestMsgDictionary) } else { - latest = Message(message: channel?["latest"] as? [String: AnyObject]) + latest = Message(ts: channel?["latest"] as? String) } } - internal init?(id:String?) { + internal init(id:String?) { self.id = id created = nil creator = nil diff --git a/SlackKit/Sources/Client+EventDispatching.swift b/SlackKit/Sources/Client+EventDispatching.swift index e7fcab1..8e47e20 100644 --- a/SlackKit/Sources/Client+EventDispatching.swift +++ b/SlackKit/Sources/Client+EventDispatching.swift @@ -25,142 +25,145 @@ internal extension Client { func dispatch(event: [String: AnyObject]) { let event = Event(event: event) - if let type = event.type { - switch type { - case .Hello: - connected = true - slackEventsDelegate?.clientConnected() - case .Ok: - messageSent(event) - case .Message: - if (event.subtype != nil) { - messageDispatcher(event) - } else { - messageReceived(event) - } - case .UserTyping: - userTyping(event) - case .ChannelMarked, .IMMarked, .GroupMarked: - channelMarked(event) - case .ChannelCreated, .IMCreated: - channelCreated(event) - case .ChannelJoined, .GroupJoined: - channelJoined(event) - case .ChannelLeft, .GroupLeft: - channelLeft(event) - case .ChannelDeleted: - channelDeleted(event) - case .ChannelRenamed, .GroupRename: - channelRenamed(event) - case .ChannelArchive, .GroupArchive: - channelArchived(event, archived: true) - case .ChannelUnarchive, .GroupUnarchive: - channelArchived(event, archived: false) - case .ChannelHistoryChanged, .IMHistoryChanged, .GroupHistoryChanged: - channelHistoryChanged(event) - case .DNDUpdated: - doNotDisturbUpdated(event) - case .DNDUpatedUser: - doNotDisturbUserUpdated(event) - case .IMOpen, .GroupOpen: - open(event, open: true) - case .IMClose, .GroupClose: - open(event, open: false) - case .FileCreated: - processFile(event) - case .FileShared: - processFile(event) - case .FileUnshared: - processFile(event) - case .FilePublic: - processFile(event) - case .FilePrivate: - filePrivate(event) - case .FileChanged: - processFile(event) - case .FileDeleted: - deleteFile(event) - case .FileCommentAdded: - fileCommentAdded(event) - case .FileCommentEdited: - fileCommentEdited(event) - case .FileCommentDeleted: - fileCommentDeleted(event) - case .PinAdded: - pinAdded(event) - case .PinRemoved: - pinRemoved(event) - case .Pong: - pong(event) - case .PresenceChange: - presenceChange(event) - case .ManualPresenceChange: - manualPresenceChange(event) - case .PrefChange: - changePreference(event) - case .UserChange: - userChange(event) - case .TeamJoin: - teamJoin(event) - case .StarAdded: - itemStarred(event, star: true) - case .StarRemoved: - itemStarred(event, star: false) - case .ReactionAdded: - addedReaction(event) - case .ReactionRemoved: - removedReaction(event) - case .EmojiChanged: - emojiChanged(event) - case .CommandsChanged: - // This functionality is only used by our web client. - // The other APIs required to support slash command metadata are currently unstable. - // Until they are released other clients should ignore this event. - break - case .TeamPlanChange: - teamPlanChange(event) - case .TeamPrefChange: - teamPreferenceChange(event) - case .TeamRename: - teamNameChange(event) - case .TeamDomainChange: - teamDomainChange(event) - case .EmailDomainChange: - emailDomainChange(event) - case .TeamProfileChange: - teamProfileChange(event) - case .TeamProfileDelete: - teamProfileDeleted(event) - case .TeamProfileReorder: - teamProfileReordered(event) - case .BotAdded: - bot(event) - case .BotChanged: - bot(event) - case .AccountsChanged: - // The accounts_changed event is used by our web client to maintain a list of logged-in accounts. - // Other clients should ignore this event. - break - case .TeamMigrationStarted: - connect(pingInterval: pingInterval, timeout: timeout, reconnect: reconnect) - case .ReconnectURL: - // The reconnect_url event is currently unsupported and experimental. - break - case .SubteamCreated, .SubteamUpdated: - subteam(event) - case .SubteamSelfAdded: - subteamAddedSelf(event) - case.SubteamSelfRemoved: - subteamRemovedSelf(event) - case .Error: - print("Error: \(event)") - break + guard let type = event.type else { + return + } + switch type { + case .Hello: + connected = true + slackEventsDelegate?.clientConnected() + case .Ok: + messageSent(event) + case .Message: + if (event.subtype != nil) { + messageDispatcher(event) + } else { + messageReceived(event) } + case .UserTyping: + userTyping(event) + case .ChannelMarked, .IMMarked, .GroupMarked: + channelMarked(event) + case .ChannelCreated, .IMCreated: + channelCreated(event) + case .ChannelJoined, .GroupJoined: + channelJoined(event) + case .ChannelLeft, .GroupLeft: + channelLeft(event) + case .ChannelDeleted: + channelDeleted(event) + case .ChannelRenamed, .GroupRename: + channelRenamed(event) + case .ChannelArchive, .GroupArchive: + channelArchived(event, archived: true) + case .ChannelUnarchive, .GroupUnarchive: + channelArchived(event, archived: false) + case .ChannelHistoryChanged, .IMHistoryChanged, .GroupHistoryChanged: + channelHistoryChanged(event) + case .DNDUpdated: + doNotDisturbUpdated(event) + case .DNDUpatedUser: + doNotDisturbUserUpdated(event) + case .IMOpen, .GroupOpen: + open(event, open: true) + case .IMClose, .GroupClose: + open(event, open: false) + case .FileCreated: + processFile(event) + case .FileShared: + processFile(event) + case .FileUnshared: + processFile(event) + case .FilePublic: + processFile(event) + case .FilePrivate: + filePrivate(event) + case .FileChanged: + processFile(event) + case .FileDeleted: + deleteFile(event) + case .FileCommentAdded: + fileCommentAdded(event) + case .FileCommentEdited: + fileCommentEdited(event) + case .FileCommentDeleted: + fileCommentDeleted(event) + case .PinAdded: + pinAdded(event) + case .PinRemoved: + pinRemoved(event) + case .Pong: + pong(event) + case .PresenceChange: + presenceChange(event) + case .ManualPresenceChange: + manualPresenceChange(event) + case .PrefChange: + changePreference(event) + case .UserChange: + userChange(event) + case .TeamJoin: + teamJoin(event) + case .StarAdded: + itemStarred(event, star: true) + case .StarRemoved: + itemStarred(event, star: false) + case .ReactionAdded: + addedReaction(event) + case .ReactionRemoved: + removedReaction(event) + case .EmojiChanged: + emojiChanged(event) + case .CommandsChanged: + // This functionality is only used by our web client. + // The other APIs required to support slash command metadata are currently unstable. + // Until they are released other clients should ignore this event. + break + case .TeamPlanChange: + teamPlanChange(event) + case .TeamPrefChange: + teamPreferenceChange(event) + case .TeamRename: + teamNameChange(event) + case .TeamDomainChange: + teamDomainChange(event) + case .EmailDomainChange: + emailDomainChange(event) + case .TeamProfileChange: + teamProfileChange(event) + case .TeamProfileDelete: + teamProfileDeleted(event) + case .TeamProfileReorder: + teamProfileReordered(event) + case .BotAdded: + bot(event) + case .BotChanged: + bot(event) + case .AccountsChanged: + // The accounts_changed event is used by our web client to maintain a list of logged-in accounts. + // Other clients should ignore this event. + break + case .TeamMigrationStarted: + connect(pingInterval: pingInterval, timeout: timeout, reconnect: reconnect) + case .ReconnectURL: + // The reconnect_url event is currently unsupported and experimental. + break + case .SubteamCreated, .SubteamUpdated: + subteam(event) + case .SubteamSelfAdded: + subteamAddedSelf(event) + case.SubteamSelfRemoved: + subteamRemovedSelf(event) + case .Error: + print("Error: \(event)") + break } } func messageDispatcher(event:Event) { - let subtype = MessageSubtype(rawValue: event.subtype!)! + guard let value = event.subtype, subtype = MessageSubtype(rawValue:value) else { + return + } switch subtype { case .MessageChanged: messageChanged(event) diff --git a/SlackKit/Sources/Client+EventHandling.swift b/SlackKit/Sources/Client+EventHandling.swift index c4b1266..d74f02b 100644 --- a/SlackKit/Sources/Client+EventHandling.swift +++ b/SlackKit/Sources/Client+EventHandling.swift @@ -32,519 +32,532 @@ internal extension Client { //MARK: - Messages func messageSent(event: Event) { - if let reply = event.replyTo, message = sentMessages[NSNumber(double: reply).stringValue], channel = message.channel, ts = message.ts { - message.ts = event.ts - message.text = event.text - channels[channel]?.messages[ts] = message - - messageEventsDelegate?.messageSent(message) + guard let reply = event.replyTo, message = sentMessages[NSNumber(double: reply).stringValue], channel = message.channel, ts = message.ts else { + return } + + message.ts = event.ts + message.text = event.text + channels[channel]?.messages[ts] = message + messageEventsDelegate?.messageSent(message) } func messageReceived(event: Event) { - if let channel = event.channel, message = event.message, id = channel.id, ts = message.ts { - channels[id]?.messages[ts] = message - - messageEventsDelegate?.messageReceived(message) + guard let channel = event.channel, message = event.message, id = channel.id, ts = message.ts else { + return } + + channels[id]?.messages[ts] = message + messageEventsDelegate?.messageReceived(message) } func messageChanged(event: Event) { - if let id = event.channel?.id, nested = event.nestedMessage, ts = nested.ts { - channels[id]?.messages[ts] = nested - - messageEventsDelegate?.messageChanged(nested) + guard let id = event.channel?.id, nested = event.nestedMessage, ts = nested.ts else { + return } + + channels[id]?.messages[ts] = nested + messageEventsDelegate?.messageChanged(nested) } func messageDeleted(event: Event) { - if let id = event.channel?.id, key = event.message?.deletedTs { - let message = channels[id]?.messages[key] - channels[id]?.messages.removeValueForKey(key) - - messageEventsDelegate?.messageDeleted(message) + guard let id = event.channel?.id, key = event.message?.deletedTs, message = channels[id]?.messages[key] else { + return } + + channels[id]?.messages.removeValueForKey(key) + messageEventsDelegate?.messageDeleted(message) } //MARK: - Channels func userTyping(event: Event) { - if let channelID = event.channel?.id, userID = event.user?.id { - if let _ = channels[channelID] { - if (!channels[channelID]!.usersTyping.contains(userID)) { - channels[channelID]?.usersTyping.append(userID) - - channelEventsDelegate?.userTyping(event.channel, user: event.user) - } - } - - let timeout = dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC))) - dispatch_after(timeout, dispatch_get_main_queue()) { - if let index = self.channels[channelID]?.usersTyping.indexOf(userID) { - self.channels[channelID]?.usersTyping.removeAtIndex(index) - } + guard let channel = event.channel, channelID = channel.id, user = event.user, userID = user.id where + channels.indexForKey(channelID) != nil && !channels[channelID]!.usersTyping.contains(userID) else { + return + } + + channels[channelID]?.usersTyping.append(userID) + channelEventsDelegate?.userTyping(channel, user: user) + + let timeout = dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC))) + dispatch_after(timeout, dispatch_get_main_queue()) { + if let index = self.channels[channelID]?.usersTyping.indexOf(userID) { + self.channels[channelID]?.usersTyping.removeAtIndex(index) } } } - + func channelMarked(event: Event) { - if let channel = event.channel, id = channel.id { - channels[id]?.lastRead = event.ts - - channelEventsDelegate?.channelMarked(channel, timestamp: event.ts) + guard let channel = event.channel, id = channel.id, timestamp = event.ts else { + return } - //TODO: Recalculate unreads + + channels[id]?.lastRead = event.ts + channelEventsDelegate?.channelMarked(channel, timestamp: timestamp) } func channelCreated(event: Event) { - if let channel = event.channel, id = channel.id { - channels[id] = channel - - channelEventsDelegate?.channelCreated(channel) + guard let channel = event.channel, id = channel.id else { + return } + + channels[id] = channel + channelEventsDelegate?.channelCreated(channel) } func channelDeleted(event: Event) { - if let channel = event.channel, id = channel.id { - channels.removeValueForKey(id) - - channelEventsDelegate?.channelDeleted(channel) + guard let channel = event.channel, id = channel.id else { + return } + + channels.removeValueForKey(id) + channelEventsDelegate?.channelDeleted(channel) } func channelJoined(event: Event) { - if let channel = event.channel, id = channel.id { - channels[id] = event.channel - - channelEventsDelegate?.channelJoined(channel) + guard let channel = event.channel, id = channel.id else { + return } + + channels[id] = event.channel + channelEventsDelegate?.channelJoined(channel) } func channelLeft(event: Event) { - if let channel = event.channel, id = channel.id, userID = authenticatedUser?.id { - if let index = channels[id]?.members?.indexOf(userID) { - channels[id]?.members?.removeAtIndex(index) - - channelEventsDelegate?.channelLeft(channel) - } + guard let channel = event.channel, id = channel.id else { + return + } + + if let userID = authenticatedUser?.id, index = channels[id]?.members?.indexOf(userID) { + channels[id]?.members?.removeAtIndex(index) } + channelEventsDelegate?.channelLeft(channel) } func channelRenamed(event: Event) { - if let channel = event.channel, id = channel.id { - channels[id]?.name = channel.name - - channelEventsDelegate?.channelRenamed(channel) + guard let channel = event.channel, id = channel.id else { + return } + + channels[id]?.name = channel.name + channelEventsDelegate?.channelRenamed(channel) } func channelArchived(event: Event, archived: Bool) { - if let channel = event.channel, id = channel.id { - channels[id]?.isArchived = archived - - channelEventsDelegate?.channelArchived(channel) + guard let channel = event.channel, id = channel.id else { + return } + + channels[id]?.isArchived = archived + channelEventsDelegate?.channelArchived(channel) } func channelHistoryChanged(event: Event) { - if let channel = event.channel { - //TODO: Reload chat history if there are any cached messages before latest - - channelEventsDelegate?.channelHistoryChanged(channel) + guard let channel = event.channel else { + return } + channelEventsDelegate?.channelHistoryChanged(channel) } //MARK: - Do Not Disturb func doNotDisturbUpdated(event: Event) { - if let dndStatus = event.dndStatus { - authenticatedUser?.doNotDisturbStatus = dndStatus - - doNotDisturbEventsDelegate?.doNotDisturbUpdated(dndStatus) + guard let dndStatus = event.dndStatus else { + return } + + authenticatedUser?.doNotDisturbStatus = dndStatus + doNotDisturbEventsDelegate?.doNotDisturbUpdated(dndStatus) } func doNotDisturbUserUpdated(event: Event) { - if let dndStatus = event.dndStatus, user = event.user, id = user.id { - users[id]?.doNotDisturbStatus = dndStatus - - doNotDisturbEventsDelegate?.doNotDisturbUserUpdated(dndStatus, user: user) + guard let dndStatus = event.dndStatus, user = event.user, id = user.id else { + return } + + users[id]?.doNotDisturbStatus = dndStatus + doNotDisturbEventsDelegate?.doNotDisturbUserUpdated(dndStatus, user: user) } //MARK: - IM & Group Open/Close func open(event: Event, open: Bool) { - if let channel = event.channel, id = channel.id { - channels[id]?.isOpen = open - - groupEventsDelegate?.groupOpened(channel) + guard let channel = event.channel, id = channel.id else { + return } + + channels[id]?.isOpen = open + groupEventsDelegate?.groupOpened(channel) } //MARK: - Files func processFile(event: Event) { - if let file = event.file, id = file.id { - if let comment = file.initialComment, commentID = comment.id { - if files[id]?.comments[commentID] == nil { - files[id]?.comments[commentID] = comment - } + guard let file = event.file, id = file.id else { + return + } + if let comment = file.initialComment, commentID = comment.id { + if files[id]?.comments[commentID] == nil { + files[id]?.comments[commentID] = comment } - - files[id] = file - - fileEventsDelegate?.fileProcessed(file) } + + files[id] = file + fileEventsDelegate?.fileProcessed(file) } func filePrivate(event: Event) { - if let file = event.file, id = file.id { - files[id]?.isPublic = false - - fileEventsDelegate?.fileMadePrivate(file) + guard let file = event.file, id = file.id else { + return } + + files[id]?.isPublic = false + fileEventsDelegate?.fileMadePrivate(file) } func deleteFile(event: Event) { - if let file = event.file, id = file.id { - if files[id] != nil { - files.removeValueForKey(id) - } - - fileEventsDelegate?.fileDeleted(file) + guard let file = event.file, id = file.id else { + return + } + + if files[id] != nil { + files.removeValueForKey(id) } + fileEventsDelegate?.fileDeleted(file) } func fileCommentAdded(event: Event) { - if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id { - files[id]?.comments[commentID] = comment - - fileEventsDelegate?.fileCommentAdded(file, comment: comment) + guard let file = event.file, id = file.id, comment = event.comment, commentID = comment.id else { + return } + + files[id]?.comments[commentID] = comment + fileEventsDelegate?.fileCommentAdded(file, comment: comment) } func fileCommentEdited(event: Event) { - if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id { - files[id]?.comments[commentID]?.comment = comment.comment - - fileEventsDelegate?.fileCommentEdited(file, comment: comment) + guard let file = event.file, id = file.id, comment = event.comment, commentID = comment.id else { + return } + + files[id]?.comments[commentID]?.comment = comment.comment + fileEventsDelegate?.fileCommentEdited(file, comment: comment) } func fileCommentDeleted(event: Event) { - if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id { - files[id]?.comments.removeValueForKey(commentID) - - fileEventsDelegate?.fileCommentDeleted(file, comment: comment) + guard let file = event.file, id = file.id, comment = event.comment, commentID = comment.id else { + return } + + files[id]?.comments.removeValueForKey(commentID) + fileEventsDelegate?.fileCommentDeleted(file, comment: comment) } //MARK: - Pins func pinAdded(event: Event) { - if let id = event.channelID, item = event.item { - channels[id]?.pinnedItems.append(item) - - pinEventsDelegate?.itemPinned(item, channel: channels[id]) + guard let id = event.channelID, item = event.item else { + return } + + channels[id]?.pinnedItems.append(item) + pinEventsDelegate?.itemPinned(item, channel: channels[id]) } func pinRemoved(event: Event) { - if let id = event.channelID { - if let pins = channels[id]?.pinnedItems.filter({$0 != event.item}) { - channels[id]?.pinnedItems = pins - } - - pinEventsDelegate?.itemUnpinned(event.item, channel: channels[id]) + guard let id = event.channelID, item = event.item else { + return + } + + if let pins = channels[id]?.pinnedItems.filter({$0 != item}) { + channels[id]?.pinnedItems = pins } + pinEventsDelegate?.itemUnpinned(item, channel: channels[id]) } - + //MARK: - Stars func itemStarred(event: Event, star: Bool) { - if let item = event.item, type = item.type { - switch type { - case "message": - starMessage(item, star: star) - case "file": - starFile(item, star: star) - case "file_comment": - starComment(item) - default: - break - } - - starEventsDelegate?.itemStarred(item, star: star) + guard let item = event.item, type = item.type else { + return } + switch type { + case "message": + starMessage(item, star: star) + case "file": + starFile(item, star: star) + case "file_comment": + starComment(item) + default: + break + } + + starEventsDelegate?.itemStarred(item, star: star) } func starMessage(item: Item, star: Bool) { - if let message = item.message, ts = message.ts, channel = item.channel { - if let _ = channels[channel]?.messages[ts] { - channels[channel]?.messages[ts]?.isStarred = star - } + guard let message = item.message, ts = message.ts, channel = item.channel where channels[channel]?.messages[ts] != nil else { + return } + channels[channel]?.messages[ts]?.isStarred = star } func starFile(item: Item, star: Bool) { - if let file = item.file, id = file.id { - files[id]?.isStarred = star - if let stars = files[id]?.stars { - if star == true { - files[id]?.stars = stars + 1 - } else { - if stars > 0 { - files[id]?.stars = stars - 1 - } + guard let file = item.file, id = file.id else { + return + } + + files[id]?.isStarred = star + if let stars = files[id]?.stars { + if star == true { + files[id]?.stars = stars + 1 + } else { + if stars > 0 { + files[id]?.stars = stars - 1 } } } } func starComment(item: Item) { - if let file = item.file, id = file.id, comment = item.comment, commentID = comment.id { - files[id]?.comments[commentID] = comment + guard let file = item.file, id = file.id, comment = item.comment, commentID = comment.id else { + return } + files[id]?.comments[commentID] = comment } //MARK: - Reactions 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 = channels[channel]?.messages[ts] { - if (message.reactions[key]) == nil { - message.reactions[key] = Reaction(name: event.reaction, user: userID) - } else { - message.reactions[key]?.users[userID] = userID - } - } - } - case "file": - if let id = item.file?.id, file = files[id] { - if file.reactions[key] == nil { - files[id]?.reactions[key] = Reaction(name: event.reaction, user: userID) - } else { - files[id]?.reactions[key]?.users[userID] = userID - } - } - case "file_comment": - if let id = item.file?.id, file = files[id], commentID = item.fileCommentID { - if file.comments[commentID]?.reactions[key] == nil { - files[id]?.comments[commentID]?.reactions[key] = Reaction(name: event.reaction, user: userID) - } else { - files[id]?.comments[commentID]?.reactions[key]?.users[userID] = userID - } - } - break - default: - break + guard let item = event.item, type = item.type, reaction = event.reaction, userID = event.user?.id, itemUser = event.itemUser else { + return + } + + switch type { + case "message": + guard let channel = item.channel, ts = item.ts, message = channels[channel]?.messages[ts] else { + return } - - reactionEventsDelegate?.reactionAdded(event.reaction, item: event.item, itemUser: event.itemUser) + message.reactions.append(Reaction(name: reaction, user: userID)) + case "file": + guard let id = item.file?.id else { + return + } + files[id]?.reactions.append(Reaction(name: reaction, user: userID)) + case "file_comment": + guard let id = item.file?.id, commentID = item.fileCommentID else { + return + } + files[id]?.comments[commentID]?.reactions.append(Reaction(name: reaction, user: userID)) + default: + break } + + reactionEventsDelegate?.reactionAdded(reaction, item: item, itemUser: itemUser) } - + 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 = channels[channel]?.messages[ts] { - if (message.reactions[key]) != nil { - message.reactions[key]?.users.removeValueForKey(userID) - } - if (message.reactions[key]?.users.count == 0) { - message.reactions.removeValueForKey(key) - } - } - } - case "file": - if let itemFile = item.file, id = itemFile.id, file = files[id] { - if file.reactions[key] != nil { - files[id]?.reactions[key]?.users.removeValueForKey(userID) - } - if files[id]?.reactions[key]?.users.count == 0 { - files[id]?.reactions.removeValueForKey(key) - } - } - case "file_comment": - if let id = item.file?.id, file = files[id], commentID = item.fileCommentID { - if file.comments[commentID]?.reactions[key] != nil { - files[id]?.comments[commentID]?.reactions[key]?.users.removeValueForKey(userID) - } - if files[id]?.comments[commentID]?.reactions[key]?.users.count == 0 { - files[id]?.comments[commentID]?.reactions.removeValueForKey(key) - } - } - break - default: - break + guard let item = event.item, type = item.type, key = event.reaction, userID = event.user?.id, itemUser = event.itemUser else { + return + } + + switch type { + case "message": + guard let channel = item.channel, ts = item.ts, message = channels[channel]?.messages[ts] else { + return } - - reactionEventsDelegate?.reactionAdded(event.reaction, item: event.item, itemUser: event.itemUser) + message.reactions = message.reactions.filter({$0.name != key && $0.user != userID}) + case "file": + guard let itemFile = item.file, id = itemFile.id else { + return + } + files[id]?.reactions = files[id]!.reactions.filter({$0.name != key && $0.user != userID}) + case "file_comment": + guard let id = item.file?.id, commentID = item.fileCommentID else { + return + } + files[id]?.comments[commentID]?.reactions = files[id]!.comments[commentID]!.reactions.filter({$0.name != key && $0.user != userID}) + default: + break } + + reactionEventsDelegate?.reactionRemoved(key, item: item, itemUser: itemUser) } - + //MARK: - Preferences func changePreference(event: Event) { - if let name = event.name { - authenticatedUser?.preferences?[name] = event.value - - if let value = event.value { - slackEventsDelegate?.preferenceChanged(name, value: value) - } + guard let name = event.name else { + return } + + authenticatedUser?.preferences?[name] = event.value + slackEventsDelegate?.preferenceChanged(name, value: event.value) } //Mark: - User Change func userChange(event: Event) { - if let user = event.user, id = user.id { - let preferences = users[id]?.preferences - users[id] = user - users[id]?.preferences = preferences - - slackEventsDelegate?.userChanged(user) + guard let user = event.user, id = user.id else { + return } + + let preferences = users[id]?.preferences + users[id] = user + users[id]?.preferences = preferences + slackEventsDelegate?.userChanged(user) } //MARK: - User Presence func presenceChange(event: Event) { - if let user = event.user, id = user.id { - users[id]?.presence = event.presence - - slackEventsDelegate?.presenceChanged(user, presence: event.presence) + guard let user = event.user, id = user.id, presence = event.presence else { + return } + + users[id]?.presence = event.presence + slackEventsDelegate?.presenceChanged(user, presence: presence) } //MARK: - Team func teamJoin(event: Event) { - if let user = event.user, id = user.id { - users[id] = user - - teamEventsDelegate?.teamJoined(user) + guard let user = event.user, id = user.id else { + return } + + users[id] = user + teamEventsDelegate?.teamJoined(user) } func teamPlanChange(event: Event) { - if let plan = event.plan { - team?.plan = plan - - teamEventsDelegate?.teamPlanChanged(plan) + guard let plan = event.plan else { + return } + + team?.plan = plan + teamEventsDelegate?.teamPlanChanged(plan) } func teamPreferenceChange(event: Event) { - if let name = event.name { - team?.prefs?[name] = event.value - - if let value = event.value { - teamEventsDelegate?.teamPreferencesChanged(name, value: value) - } + guard let name = event.name else { + return } + + team?.prefs?[name] = event.value + teamEventsDelegate?.teamPreferencesChanged(name, value: event.value) } func teamNameChange(event: Event) { - if let name = event.name { - team?.name = name - - teamEventsDelegate?.teamNameChanged(name) + guard let name = event.name else { + return } + + team?.name = name + teamEventsDelegate?.teamNameChanged(name) } func teamDomainChange(event: Event) { - if let domain = event.domain { - team?.domain = domain - - teamEventsDelegate?.teamDomainChanged(domain) + guard let domain = event.domain else { + return } + + team?.domain = domain + teamEventsDelegate?.teamDomainChanged(domain) } func emailDomainChange(event: Event) { - if let domain = event.emailDomain { - team?.emailDomain = domain - - teamEventsDelegate?.teamEmailDomainChanged(domain) + guard let domain = event.emailDomain else { + return } + + team?.emailDomain = domain + teamEventsDelegate?.teamEmailDomainChanged(domain) } func emojiChanged(event: Event) { - //TODO: Call emoji.list here - teamEventsDelegate?.teamEmojiChanged() } //MARK: - Bots func bot(event: Event) { - if let bot = event.bot, id = bot.id { - bots[id] = bot - - slackEventsDelegate?.botEvent(bot) + guard let bot = event.bot, id = bot.id else { + return } + + bots[id] = bot + slackEventsDelegate?.botEvent(bot) } //MARK: - Subteams func subteam(event: Event) { - if let subteam = event.subteam, id = subteam.id { - userGroups[id] = subteam - - subteamEventsDelegate?.subteamEvent(subteam) + guard let subteam = event.subteam, id = subteam.id else { + return } + userGroups[id] = subteam + subteamEventsDelegate?.subteamEvent(subteam) } func subteamAddedSelf(event: Event) { - if let subteamID = event.subteamID, _ = authenticatedUser?.userGroups { - authenticatedUser?.userGroups![subteamID] = subteamID - - subteamEventsDelegate?.subteamSelfAdded(subteamID) + guard let subteamID = event.subteamID, _ = authenticatedUser?.userGroups else { + return } + + authenticatedUser?.userGroups![subteamID] = subteamID + subteamEventsDelegate?.subteamSelfAdded(subteamID) } func subteamRemovedSelf(event: Event) { - if let subteamID = event.subteamID { - authenticatedUser?.userGroups?.removeValueForKey(subteamID) - - subteamEventsDelegate?.subteamSelfRemoved(subteamID) + guard let subteamID = event.subteamID else { + return } + + authenticatedUser?.userGroups?.removeValueForKey(subteamID) + subteamEventsDelegate?.subteamSelfRemoved(subteamID) } //MARK: - Team Profiles func teamProfileChange(event: Event) { + guard let profile = event.profile else { + return + } + for user in users { - if let fields = event.profile?.fields { - for key in fields.keys { - users[user.0]?.profile?.customProfile?.fields[key]?.updateProfileField(fields[key]) - } + for key in profile.fields.keys { + users[user.0]?.profile?.customProfile?.fields[key]?.updateProfileField(profile.fields[key]) } } - teamProfileEventsDelegate?.teamProfileChanged(event.profile) + teamProfileEventsDelegate?.teamProfileChanged(profile) } func teamProfileDeleted(event: Event) { + guard let profile = event.profile else { + return + } + for user in users { - if let id = event.profile?.fields.first?.0 { + if let id = profile.fields.first?.0 { users[user.0]?.profile?.customProfile?.fields[id] = nil } } - teamProfileEventsDelegate?.teamProfileDeleted(event.profile) + teamProfileEventsDelegate?.teamProfileDeleted(profile) } func teamProfileReordered(event: Event) { + guard let profile = event.profile else { + return + } + for user in users { - if let keys = event.profile?.fields.keys { - for key in keys { - users[user.0]?.profile?.customProfile?.fields[key]?.ordering = event.profile?.fields[key]?.ordering - } + for key in profile.fields.keys { + users[user.0]?.profile?.customProfile?.fields[key]?.ordering = profile.fields[key]?.ordering } } - - teamProfileEventsDelegate?.teamProfileReordered(event.profile) + + teamProfileEventsDelegate?.teamProfileReordered(profile) } //MARK: - Authenticated User func manualPresenceChange(event: Event) { - authenticatedUser?.presence = event.presence + guard let presence = event.presence, user = authenticatedUser else { + return + } - slackEventsDelegate?.manualPresenceChanged(authenticatedUser, presence: event.presence) + authenticatedUser?.presence = presence + slackEventsDelegate?.manualPresenceChanged(user, presence: presence) } } diff --git a/SlackKit/Sources/Client+Utilities.swift b/SlackKit/Sources/Client+Utilities.swift index e2c43bd..e690852 100644 --- a/SlackKit/Sources/Client+Utilities.swift +++ b/SlackKit/Sources/Client+Utilities.swift @@ -23,15 +23,26 @@ import Foundation -public extension Client { +public enum ClientError: ErrorType { + case ChannelDoesNotExist + case UserDoesNotExist +} +public extension Client { + //MARK: - User & Channel - public func getChannelIDByName(name: String) -> String? { - return channels.filter{$0.1.name == stripString(name)}.first?.0 + public func getChannelIDByName(name: String) throws -> String { + guard let id = channels.filter({$0.1.name == stripString(name)}).first?.0 else { + throw ClientError.ChannelDoesNotExist + } + return id } - public func getUserIDByName(name: String) -> String? { - return users.filter{$0.1.name == stripString(name)}.first?.0 + public func getUserIDByName(name: String) throws -> String { + guard let id = users.filter({$0.1.name == stripString(name)}).first?.0 else { + throw ClientError.UserDoesNotExist + } + return id } public func getImIDForUserWithID(id: String, success: (imID: String?)->Void, failure: (error: SlackError)->Void) { @@ -45,7 +56,7 @@ public extension Client { } //MARK: - Utilities - internal func stripString(string: String) -> String? { + internal func stripString(string: String) -> String { var strippedString = string if string[string.startIndex] == "@" || string[string.startIndex] == "#" { strippedString = string.substringFromIndex(string.startIndex.advancedBy(1)) diff --git a/SlackKit/Sources/Client.swift b/SlackKit/Sources/Client.swift index 0dbe560..b483617 100644 --- a/SlackKit/Sources/Client.swift +++ b/SlackKit/Sources/Client.swift @@ -27,7 +27,6 @@ import Starscream public class Client: WebSocketDelegate { internal(set) public var connected = false - internal(set) public var authenticated = false internal(set) public var authenticatedUser: User? internal(set) public var team: Team? @@ -101,16 +100,15 @@ public class Client: WebSocketDelegate { //MARK: - RTM Message send public func sendMessage(message: String, channelID: String) { - if (connected) { - if let data = formatMessageToSlackJsonString(msg: message, channel: channelID) { - if let string = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { - webSocket?.writeString(string) - } - } + guard connected else { return } + + if let data = try? formatMessageToSlackJsonString(msg: message, channel: channelID), + string = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { + webSocket?.writeString(string) } } - private func formatMessageToSlackJsonString(message: (msg: String, channel: String)) -> NSData? { + private func formatMessageToSlackJsonString(message: (msg: String, channel: String)) throws -> NSData { let json: [String: AnyObject] = [ "id": NSDate().slackTimestamp(), "type": "message", @@ -118,54 +116,49 @@ public class Client: WebSocketDelegate { "text": message.msg.slackFormatEscaping() ] addSentMessage(json) - do { - let data = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions.PrettyPrinted) - return data - } - catch _ { - return nil - } + return try NSJSONSerialization.dataWithJSONObject(json, options: []) } private func addSentMessage(dictionary: [String: AnyObject]) { var message = dictionary - let ts = message["id"] as? NSNumber + guard let id = message["id"] as? NSNumber else { + return + } + let ts = String(id) message.removeValueForKey("id") - message["ts"] = ts?.stringValue + message["ts"] = ts message["user"] = self.authenticatedUser?.id - sentMessages[ts!.stringValue] = Message(message: message) + sentMessages[ts] = Message(message: message) } //MARK: - RTM Ping private func pingRTMServerAtInterval(interval: NSTimeInterval) { let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC))) dispatch_after(delay, pingPongQueue, { - if self.connected && self.timeoutCheck() { - self.sendRTMPing() - self.pingRTMServerAtInterval(interval) - } else { + guard self.connected && self.timeoutCheck() else { self.disconnect() + return } + self.sendRTMPing() + self.pingRTMServerAtInterval(interval) }) } private func sendRTMPing() { - if connected { - let json: [String: AnyObject] = [ - "id": NSDate().slackTimestamp(), - "type": "ping", - ] - do { - let data = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions.PrettyPrinted) - let string = NSString(data: data, encoding: NSUTF8StringEncoding) - if let writePing = string as? String { - ping = json["id"] as? Double - webSocket?.writeString(writePing) - } - } - catch _ { - - } + guard connected else { + return + } + let json: [String: AnyObject] = [ + "id": NSDate().slackTimestamp(), + "type": "ping", + ] + guard let data = try? NSJSONSerialization.dataWithJSONObject(json, options: []) else { + return + } + let string = NSString(data: data, encoding: NSUTF8StringEncoding) + if let writePing = string as? String { + ping = json["id"] as? Double + webSocket?.writeString(writePing) } } @@ -197,19 +190,22 @@ public class Client: WebSocketDelegate { } private func addUser(aUser: [String: AnyObject]) { - if let user = User(user: aUser), id = user.id { + let user = User(user: aUser) + if let id = user.id { users[id] = user } } private func addChannel(aChannel: [String: AnyObject]) { - if let channel = Channel(channel: aChannel), id = channel.id { + let channel = Channel(channel: aChannel) + if let id = channel.id { channels[id] = channel } } private func addBot(aBot: [String: AnyObject]) { - if let bot = Bot(bot: aBot), id = bot.id { + let bot = Bot(bot: aBot) + if let id = bot.id { bots[id] = bot } } @@ -219,7 +215,7 @@ public class Client: WebSocketDelegate { if let all = subteams["all"] as? [[String: AnyObject]] { for item in all { let u = UserGroup(userGroup: item) - self.userGroups[u!.id!] = u + self.userGroups[u.id!] = u } } if let auth = subteams["self"] as? [String] { @@ -251,7 +247,6 @@ public class Client: WebSocketDelegate { public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { connected = false - authenticated = false webSocket = nil authenticatedUser = nil slackEventsDelegate?.clientDisconnected() @@ -264,16 +259,12 @@ public class Client: WebSocketDelegate { guard let data = text.dataUsingEncoding(NSUTF8StringEncoding) else { return } - do { - if let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as? [String: AnyObject] { - dispatch(json) - } - } - catch _ { - + + if let json = (try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments)) as? [String: AnyObject] { + dispatch(json) } } - + public func websocketDidReceiveData(socket: WebSocket, data: NSData) {} } diff --git a/SlackKit/Sources/Event.swift b/SlackKit/Sources/Event.swift index dd062c1..3272600 100644 --- a/SlackKit/Sources/Event.swift +++ b/SlackKit/Sources/Event.swift @@ -197,31 +197,27 @@ internal struct Event { message = Message(message: event) nestedMessage = Message(message: event["message"] as? [String: AnyObject]) profile = CustomProfile(profile: event["profile"] as? [String: AnyObject]) - - // Comment, Channel, User, and File can come across as Strings or Dictionaries - if (Comment(comment: event["comment"] as? [String: AnyObject])?.id == nil) { - comment = Comment(id: event["comment"] as? String) + file = File(id: event["file"] as? String) + + // Comment, Channel, and User can come across as Strings or Dictionaries + if let commentDictionary = event["comment"] as? [String: AnyObject] { + comment = Comment(comment: commentDictionary) } else { - comment = Comment(comment: event["comment"] as? [String: AnyObject]) + comment = Comment(id: event["comment"] as? String) } - - if (User(user: event["user"] as? [String: AnyObject])?.id == nil) { - user = User(id: event["user"] as? String) + + if let userDictionary = event["user"] as? [String: AnyObject] { + user = User(user: userDictionary) } else { - user = User(user: event["user"] as? [String: AnyObject]) + user = User(id: event["user"] as? String) } - - if (File(file: event["file"] as? [String: AnyObject])?.id == nil) { - file = File(id: event["file"] as? String) + + if let channelDictionary = event["channel"] as? [String: AnyObject] { + channel = Channel(channel: channelDictionary) } else { - file = File(file: event["file"] as? [String: AnyObject]) - } - - if (Channel(channel: event["channel"] as? [String: AnyObject])?.id == nil) { channel = Channel(id: event["channel"] as? String) - } else { - channel = Channel(channel: event["channel"] as? [String: AnyObject]) } + } } diff --git a/SlackKit/Sources/EventDelegate.swift b/SlackKit/Sources/EventDelegate.swift index 1d72d26..f21e6fd 100644 --- a/SlackKit/Sources/EventDelegate.swift +++ b/SlackKit/Sources/EventDelegate.swift @@ -27,10 +27,10 @@ public protocol SlackEventsDelegate: class { func clientConnectionFailed(error: SlackError) func clientConnected() func clientDisconnected() - func preferenceChanged(preference: String, value: AnyObject) + func preferenceChanged(preference: String, value: AnyObject?) func userChanged(user: User) - func presenceChanged(user: User?, presence: String?) - func manualPresenceChanged(user: User?, presence: String?) + func presenceChanged(user: User, presence: String) + func manualPresenceChanged(user: User, presence: String) func botEvent(bot: Bot) } @@ -42,8 +42,8 @@ public protocol MessageEventsDelegate: class { } public protocol ChannelEventsDelegate: class { - func userTyping(channel: Channel?, user: User?) - func channelMarked(channel: Channel, timestamp: String?) + func userTyping(channel: Channel, user: User) + func channelMarked(channel: Channel, timestamp: String) func channelCreated(channel: Channel) func channelDeleted(channel: Channel) func channelRenamed(channel: Channel) @@ -55,7 +55,7 @@ public protocol ChannelEventsDelegate: class { public protocol DoNotDisturbEventsDelegate: class { func doNotDisturbUpdated(dndStatus: DoNotDisturbStatus) - func doNotDisturbUserUpdated(dndStatus: DoNotDisturbStatus, user: User?) + func doNotDisturbUserUpdated(dndStatus: DoNotDisturbStatus, user: User) } public protocol GroupEventsDelegate: class { @@ -72,8 +72,8 @@ public protocol FileEventsDelegate: class { } public protocol PinEventsDelegate: class { - func itemPinned(item: Item?, channel: Channel?) - func itemUnpinned(item: Item?, channel: Channel?) + func itemPinned(item: Item, channel: Channel?) + func itemUnpinned(item: Item, channel: Channel?) } public protocol StarEventsDelegate: class { @@ -81,14 +81,14 @@ public protocol StarEventsDelegate: class { } public protocol ReactionEventsDelegate: class { - func reactionAdded(reaction: String?, item: Item?, itemUser: String?) - func reactionRemoved(reaction: String?, item: Item?, itemUser: String?) + func reactionAdded(reaction: String, item: Item, itemUser: String) + func reactionRemoved(reaction: String, item: Item, itemUser: String) } public protocol TeamEventsDelegate: class { func teamJoined(user: User) func teamPlanChanged(plan: String) - func teamPreferencesChanged(preference: String, value: AnyObject) + func teamPreferencesChanged(preference: String, value: AnyObject?) func teamNameChanged(name: String) func teamDomainChanged(domain: String) func teamEmailDomainChanged(domain: String) @@ -102,7 +102,7 @@ public protocol SubteamEventsDelegate: class { } public protocol TeamProfileEventsDelegate: class { - func teamProfileChanged(profile: CustomProfile?) - func teamProfileDeleted(profile: CustomProfile?) - func teamProfileReordered(profile: CustomProfile?) + func teamProfileChanged(profile: CustomProfile) + func teamProfileDeleted(profile: CustomProfile) + func teamProfileReordered(profile: CustomProfile) } diff --git a/SlackKit/Sources/Extensions.swift b/SlackKit/Sources/Extensions.swift index 9c5520c..adf12ad 100644 --- a/SlackKit/Sources/Extensions.swift +++ b/SlackKit/Sources/Extensions.swift @@ -41,19 +41,3 @@ internal extension String { } } - -internal extension Array { - - func objectArrayFromDictionaryArray(intializer:([String: AnyObject])->T?) -> [T] { - var returnValue = [T]() - for object in self { - if let dictionary = object as? [String: AnyObject] { - if let value = intializer(dictionary) { - returnValue.append(value) - } - } - } - return returnValue - } - -} diff --git a/SlackKit/Sources/File.swift b/SlackKit/Sources/File.swift index 2f82635..8fd6e21 100644 --- a/SlackKit/Sources/File.swift +++ b/SlackKit/Sources/File.swift @@ -76,9 +76,9 @@ public struct File { internal(set) public var isStarred: Bool? internal(set) public var pinnedTo: [String]? internal(set) public var comments = [String: Comment]() - internal(set) public var reactions = [String: Reaction]() + internal(set) public var reactions = [Reaction]() - public init?(file:[String: AnyObject]?) { + public init(file:[String: AnyObject]?) { id = file?["id"] as? String created = file?["created"] as? Int name = file?["name"] as? String @@ -131,13 +131,10 @@ public struct File { stars = file?["num_stars"] as? Int isStarred = file?["is_starred"] as? Bool pinnedTo = file?["pinned_to"] as? [String] - if let reactions = file?["reactions"] as? [[String: AnyObject]] { - self.reactions = Reaction.reactionsFromArray(reactions) - } - + reactions = Reaction.reactionsFromArray(file?["reactions"] as? [[String: AnyObject]]) } - internal init?(id:String?) { + internal init(id:String?) { self.id = id created = nil name = nil diff --git a/SlackKit/Sources/Message.swift b/SlackKit/Sources/Message.swift index 763dd44..7cf39b2 100644 --- a/SlackKit/Sources/Message.swift +++ b/SlackKit/Sources/Message.swift @@ -45,10 +45,10 @@ public class Message { internal(set) var pinnedTo: [String]? public let comment: Comment? public let file: File? - internal(set) public var reactions = [String: Reaction]() + internal(set) public var reactions = [Reaction]() internal(set) public var attachments: [Attachment]? - public init?(message: [String: AnyObject]?) { + public init(message: [String: AnyObject]?) { subtype = message?["subtype"] as? String ts = message?["ts"] as? String user = message?["user"] as? String @@ -70,13 +70,13 @@ public class Message { pinnedTo = message?["pinned_to"] as? [String] comment = Comment(comment: message?["comment"] as? [String: AnyObject]) file = File(file: message?["file"] as? [String: AnyObject]) - reactions = messageReactions(message?["reactions"] as? [[String: AnyObject]]) - attachments = (message?["attachments"] as? [[String: AnyObject]])?.objectArrayFromDictionaryArray({(attachment) -> Attachment? in + reactions = Reaction.reactionsFromArray(message?["reactions"] as? [[String: AnyObject]]) + attachments = (message?["attachments"] as? [[String: AnyObject]])?.map({(attachment) -> Attachment in return Attachment(attachment: attachment) }) } - internal init?(ts:String?) { + internal init(ts:String?) { self.ts = ts subtype = nil user = nil @@ -90,14 +90,7 @@ public class Message { comment = nil file = nil } - - private func messageReactions(reactions: [[String: AnyObject]]?) -> [String: Reaction] { - var returnValue = [String: Reaction]() - if let r = reactions { - returnValue = Reaction.reactionsFromArray(r) - } - return returnValue - } + } extension Message: Equatable {} diff --git a/SlackKit/Sources/NetworkInterface.swift b/SlackKit/Sources/NetworkInterface.swift index 5223fe4..d924020 100644 --- a/SlackKit/Sources/NetworkInterface.swift +++ b/SlackKit/Sources/NetworkInterface.swift @@ -32,37 +32,18 @@ internal struct NetworkInterface { if let params = parameters { requestString += requestStringFromParameters(params) } - let request = NSURLRequest(URL: NSURL(string: requestString)!) + guard let url = NSURL(string: requestString) else { + errorClosure(SlackError.ClientNetworkError) + return + } + let request = NSURLRequest(URL:url) NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, internalError) -> Void in - guard let data = data, response = response as? NSHTTPURLResponse else { - errorClosure(SlackError.ClientNetworkError) - return - } - do { - if response.statusCode == 200 { - 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 - } - } - } else if response.statusCode == 429 { - throw SlackError.TooManyRequests - } else { - throw SlackError.UnknownHTTPError - } - } catch let error { - if let slackError = error as? SlackError { - errorClosure(slackError) - } else { - errorClosure(SlackError.UnknownError) - } - } + self.handleResponse(data, response: response, internalError: internalError, successClosure: {(json) in + successClosure(json) + }, errorClosure: {(error) in + errorClosure(error) + }) }.resume() } @@ -71,8 +52,11 @@ internal struct NetworkInterface { if let params = parameters { requestString = requestString + requestStringFromParameters(params) } - - let request = NSMutableURLRequest(URL: NSURL(string: requestString)!) + guard let url = NSURL(string: requestString) else { + errorClosure(SlackError.ClientNetworkError) + return + } + let request = NSMutableURLRequest(URL:url) request.HTTPMethod = "POST" let boundaryConstant = randomBoundary() let contentType = "multipart/form-data; boundary=" + boundaryConstant @@ -94,28 +78,48 @@ internal struct NetworkInterface { NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, internalError) -> Void in - guard let data = data else { + self.handleResponse(data, response: response, internalError: internalError, successClosure: {(json) in + successClosure(json) + }, errorClosure: {(error) in + errorClosure(error) + }) + }.resume() + } + + private func handleResponse(data: NSData?, response:NSURLResponse?, internalError:NSError?, successClosure: ([String: AnyObject])->Void, errorClosure: (SlackError)->Void) { + guard let data = data, response = response as? NSHTTPURLResponse else { + errorClosure(SlackError.ClientNetworkError) + return + } + do { + guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] else { + errorClosure(SlackError.ClientJSONError) return } - do { - let result = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject] - if (result["ok"] as! Bool == true) { - successClosure(result) + + switch response.statusCode { + case 200: + if (json["ok"] as! Bool == true) { + successClosure(json) } else { - if let errorString = result["error"] as? String { + if let errorString = json["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) - } + case 429: + throw SlackError.TooManyRequests + default: + throw SlackError.ClientNetworkError } - }.resume() + } catch let error { + if let slackError = error as? SlackError { + errorClosure(slackError) + } else { + errorClosure(SlackError.UnknownError) + } + } } private func randomBoundary() -> String { diff --git a/SlackKit/Sources/SlackWebAPI.swift b/SlackKit/Sources/SlackWebAPI.swift index 63eb87f..660b51f 100644 --- a/SlackKit/Sources/SlackWebAPI.swift +++ b/SlackKit/Sources/SlackWebAPI.swift @@ -138,7 +138,7 @@ public class SlackWebAPI { } //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: History?)->Void)?, failure: FailureClosure?) { + public func channelHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History)->Void)?, failure: FailureClosure?) { history(.ChannelsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in success?(history: history) @@ -147,7 +147,7 @@ public class SlackWebAPI { } } - public func channelInfo(id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) { + public func channelInfo(id: String, success: ((channel: Channel)->Void)?, failure: FailureClosure?) { info(.ChannelsInfo, type:ChannelType.Channel, id: id, success: { (channel) -> Void in success?(channel: channel) @@ -223,7 +223,7 @@ public class SlackWebAPI { } //MARK: - Do Not Disturb - public func dndInfo(user: String? = nil, success: ((status: DoNotDisturbStatus?)->Void)?, failure: FailureClosure?) { + public func dndInfo(user: String? = nil, success: ((status: DoNotDisturbStatus)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject?] = ["user": user] networkInterface.request(.DNDInfo, token: token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in @@ -233,11 +233,15 @@ public class SlackWebAPI { } } - public func dndTeamInfo(users: [String]? = nil, success: ((statuses: [String: DoNotDisturbStatus]?)->Void)?, failure: FailureClosure?) { + public func dndTeamInfo(users: [String]? = nil, success: ((statuses: [String: DoNotDisturbStatus])->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject?] = ["users":users?.joinWithSeparator(",")] networkInterface.request(.DNDTeamInfo, token: token, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in - success?(statuses: self.enumerateDNDStauses(response["users"] as? [String: AnyObject])) + guard let usersDictionary = response["users"] as? [String: AnyObject] else { + success?(statuses: [:]) + return + } + success?(statuses: self.enumerateDNDStatuses(usersDictionary)) }) {(error) -> Void in failure?(error: error) } @@ -264,24 +268,24 @@ public class SlackWebAPI { } } - public func fileInfo(fileID: String, commentCount: Int = 100, totalPages: Int = 1, success: ((file: File?)->Void)?, failure: FailureClosure?) { + public func fileInfo(fileID: String, commentCount: Int = 100, totalPages: Int = 1, success: ((file: File)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["file":fileID, "count": commentCount, "totalPages":totalPages] networkInterface.request(.FilesInfo, token: token, parameters: parameters, successClosure: { (response) in var file = File(file: response["file"] as? [String: AnyObject]) - (response["comments"] as? [[String: AnyObject]])?.objectArrayFromDictionaryArray({(comment) -> Comment? in - if let comment = Comment(comment: comment), id = comment.id { - file?.comments[id] = comment + (response["comments"] as? [[String: AnyObject]])?.forEach { comment in + let comment = Comment(comment: comment) + if let id = comment.id { + file.comments[id] = comment } - return nil - }) + } success?(file: file) }) {(error) in 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: FailureClosure?) { + 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(",")] networkInterface.uploadRequest(token, data: file, parameters: filterNilParameters(parameters), successClosure: { (response) -> Void in @@ -292,7 +296,7 @@ public class SlackWebAPI { } //MARK: - File Comments - public func addFileComment(fileID: String, comment: String, success: ((comment: Comment?)->Void)?, failure: FailureClosure?) { + public func addFileComment(fileID: String, comment: String, success: ((comment: Comment)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["file":fileID, "comment":comment.slackFormatEscaping()] networkInterface.request(.FilesCommentsAdd, token: token, parameters: parameters, successClosure: { (response) -> Void in @@ -302,7 +306,7 @@ public class SlackWebAPI { } } - public func editFileComment(fileID: String, commentID: String, comment: String, success: ((comment: Comment?)->Void)?, failure: FailureClosure?) { + public func editFileComment(fileID: String, commentID: String, comment: String, success: ((comment: Comment)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["file":fileID, "id":commentID, "comment":comment.slackFormatEscaping()] networkInterface.request(.FilesCommentsEdit, token: token, parameters: parameters, successClosure: { (response) -> Void in @@ -332,7 +336,7 @@ public class SlackWebAPI { } } - public func groupHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History?)->Void)?, failure: FailureClosure?) { + public func groupHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History)->Void)?, failure: FailureClosure?) { history(.GroupsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in success?(history: history) @@ -341,7 +345,7 @@ public class SlackWebAPI { } } - public func groupInfo(id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) { + public func groupInfo(id: String, success: ((channel: Channel)->Void)?, failure: FailureClosure?) { info(.GroupsInfo, type:ChannelType.Group, id: id, success: { (channel) -> Void in success?(channel: channel) @@ -406,7 +410,7 @@ public class SlackWebAPI { } } - public func imHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History?)->Void)?, failure: FailureClosure?) { + public func imHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History)->Void)?, failure: FailureClosure?) { history(.IMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in success?(history: history) @@ -454,7 +458,7 @@ public class SlackWebAPI { } } - public func mpimHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History?)->Void)?, failure: FailureClosure?) { + public func mpimHistory(id: String, latest: String = "\(NSDate().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History)->Void)?, failure: FailureClosure?) { history(.MPIMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: { (history) -> Void in success?(history: history) @@ -605,7 +609,7 @@ public class SlackWebAPI { } } - public func userInfo(id: String, success: ((user: User?)->Void)?, failure: FailureClosure?) { + public func userInfo(id: String, success: ((user: User)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["user":id] networkInterface.request(.UsersInfo, token: token, parameters: parameters, successClosure: { (response) -> Void in @@ -655,7 +659,7 @@ public class SlackWebAPI { } } - 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: History?)->Void)?, failure: FailureClosure?) { + 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: History)->Void)?, failure: FailureClosure?) { let parameters: [String: AnyObject] = ["channel": id, "latest": latest, "oldest": oldest, "inclusive":inclusive, "count":count, "unreads":unreads] networkInterface.request(endpoint, token: token, parameters: parameters, successClosure: { (response) -> Void in @@ -665,7 +669,7 @@ public class SlackWebAPI { } } - private func info(endpoint: SlackAPIEndpoint, type: ChannelType, 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] networkInterface.request(endpoint, token: token, parameters: parameters, successClosure: { (response) -> Void in @@ -726,7 +730,7 @@ public class SlackWebAPI { } } do { - let data = try NSJSONSerialization.dataWithJSONObject(attachmentArray, options: NSJSONWritingOptions.PrettyPrinted) + let data = try NSJSONSerialization.dataWithJSONObject(attachmentArray, options: []) let string = NSString(data: data, encoding: NSUTF8StringEncoding) return string } catch _ { @@ -735,14 +739,12 @@ public class SlackWebAPI { } return nil } - - //MARK: - Enumerate Do Not Distrub Status - private func enumerateDNDStauses(statuses: [String: AnyObject]?) -> [String: DoNotDisturbStatus] { + + //MARK: - Enumerate Do Not Disturb Status + private func enumerateDNDStatuses(statuses: [String: AnyObject]) -> [String: DoNotDisturbStatus] { var retVal = [String: DoNotDisturbStatus]() - if let keys = statuses?.keys { - for key in keys { - retVal[key] = DoNotDisturbStatus(status: statuses?[key] as? [String: AnyObject]) - } + for key in statuses.keys { + retVal[key] = DoNotDisturbStatus(status: statuses[key] as? [String: AnyObject]) } return retVal } diff --git a/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift b/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift index 3d7934f..2dd846f 100644 --- a/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift +++ b/SlackKit/Sources/SlackWebAPIErrorDispatcher.swift @@ -113,6 +113,7 @@ public enum SlackError: ErrorType { case UserNotVisible // Client case ClientNetworkError + case ClientJSONError // HTTP case TooManyRequests case UnknownHTTPError diff --git a/SlackKit/Sources/Team.swift b/SlackKit/Sources/Team.swift index eac27fa..51ca368 100644 --- a/SlackKit/Sources/Team.swift +++ b/SlackKit/Sources/Team.swift @@ -33,7 +33,7 @@ public struct Team { internal(set) public var plan: String? internal(set) public var icon: TeamIcon? - internal init?(team: [String: AnyObject]?) { + internal init(team: [String: AnyObject]?) { id = team?["id"] as! String name = team?["name"] as? String domain = team?["domain"] as? String @@ -56,7 +56,7 @@ public struct TeamIcon { internal(set) public var imageOriginal: String? internal(set) public var imageDefault: Bool? - internal init?(icon: [String: AnyObject]?) { + internal init(icon: [String: AnyObject]?) { image34 = icon?["image_34"] as? String image44 = icon?["image_44"] as? String image68 = icon?["image_68"] as? String diff --git a/SlackKit/Sources/Types.swift b/SlackKit/Sources/Types.swift index c24ba2b..8573aa0 100644 --- a/SlackKit/Sources/Types.swift +++ b/SlackKit/Sources/Types.swift @@ -28,7 +28,7 @@ public struct Edited { public let user: String? public let ts: String? - internal init?(edited:[String: AnyObject]?) { + internal init(edited:[String: AnyObject]?) { user = edited?["user"] as? String ts = edited?["ts"] as? String } @@ -40,15 +40,13 @@ public struct History { internal(set) public var messages = [Message]() public let hasMore: Bool? - internal init?(history: [String: AnyObject]?) { + internal init(history: [String: AnyObject]?) { if let latestStr = history?["latest"] as? String, latestDouble = Double(latestStr) { latest = NSDate(timeIntervalSince1970: NSTimeInterval(latestDouble)) } if let msgs = history?["messages"] as? [[String: AnyObject]] { for message in msgs { - if let message = Message(message: message) { - messages.append(message) - } + messages.append(Message(message: message)) } } hasMore = history?["has_more"] as? Bool @@ -58,34 +56,27 @@ public struct History { // MARK: - Reaction public struct Reaction { public let name: String? - internal(set) public var users = [String: String]() + internal(set) public var user: String? - internal init?(reaction:[String: AnyObject]?) { + internal init(reaction:[String: AnyObject]?) { name = reaction?["name"] as? String } - internal init?(name: String?, user: String) { - self.name = name - users[user] = user - } - - internal init?(name: String?, users: [String: String]) { + internal init(name: String, user: String) { self.name = name - self.users = users + self.user = user } - static func reactionsFromArray(array: [[String: AnyObject]]) -> [String: Reaction] { - var reactions = [String: Reaction]() - var userDictionary = [String: String]() - for reaction in array { - if let users = reaction["users"] as? [String] { - for user in users { - userDictionary[user] = user + static func reactionsFromArray(array: [[String: AnyObject]]?) -> [Reaction] { + var reactions = [Reaction]() + if let array = array { + for reaction in array { + if let users = reaction["users"] as? [String], name = reaction["name"] as? String { + for user in users { + reactions.append(Reaction(name: name, user: user)) + } } } - if let name = reaction["name"] as? String { - reactions[name] = Reaction(name: name, users: userDictionary) - } } return reactions } @@ -106,9 +97,9 @@ public struct Comment { internal(set) public var comment: String? internal(set) public var starred: Bool? internal(set) public var stars: Int? - internal(set) public var reactions = [String: Reaction]() + internal(set) public var reactions = [Reaction]() - internal init?(comment:[String: AnyObject]?) { + internal init(comment:[String: AnyObject]?) { id = comment?["id"] as? String created = comment?["created"] as? Int user = comment?["user"] as? String @@ -117,7 +108,7 @@ public struct Comment { self.comment = comment?["comment"] as? String } - internal init?(id: String?) { + internal init(id: String?) { self.id = id self.user = nil } @@ -139,7 +130,7 @@ public struct Item { public let comment: Comment? public let fileCommentID: String? - internal init?(item:[String: AnyObject]?) { + internal init(item:[String: AnyObject]?) { type = item?["type"] as? String ts = item?["ts"] as? String channel = item?["channel"] as? String @@ -147,15 +138,16 @@ public struct Item { message = Message(message: item?["message"] as? [String: AnyObject]) // Comment and File can come across as Strings or Dictionaries - if (Comment(comment: item?["comment"] as? [String: AnyObject])?.id == nil) { - comment = Comment(id: item?["comment"] as? String) + if let commentDictionary = item?["comment"] as? [String: AnyObject] { + comment = Comment(comment: commentDictionary) } else { - comment = Comment(comment: item?["comment"] as? [String: AnyObject]) + comment = Comment(id: item?["comment"] as? String) } - if (File(file: item?["file"] as? [String: AnyObject])?.id == nil) { - file = File(id: item?["file"] as? String) + + if let fileDictionary = item?["file"] as? [String: AnyObject] { + file = File(file: fileDictionary) } else { - file = File(file: item?["file"] as? [String: AnyObject]) + file = File(id: item?["file"] as? String) } fileCommentID = item?["file_comment"] as? String @@ -174,7 +166,7 @@ public struct Topic { public let creator: String? public let lastSet: Int? - internal init?(topic: [String: AnyObject]?) { + internal init(topic: [String: AnyObject]?) { value = topic?["value"] as? String creator = topic?["creator"] as? String lastSet = topic?["last_set"] as? Int @@ -189,7 +181,7 @@ public struct DoNotDisturbStatus { internal(set) public var snoozeEnabled: Bool? internal(set) public var snoozeEndtime: Int? - internal init?(status: [String: AnyObject]?) { + internal init(status: [String: AnyObject]?) { enabled = status?["dnd_enabled"] as? Bool nextDoNotDisturbStart = status?["next_dnd_start_ts"] as? Int nextDoNotDisturbEnd = status?["next_dnd_end_ts"] as? Int @@ -203,26 +195,25 @@ public struct DoNotDisturbStatus { public struct CustomProfile { internal(set) public var fields = [String: CustomProfileField]() - internal init?(profile: [String: AnyObject]?) { + internal init(profile: [String: AnyObject]?) { if let eventFields = profile?["fields"] as? [AnyObject] { for field in eventFields { - if let cpf = CustomProfileField(field: field as? [String: AnyObject]), id = cpf.id { - fields[id] = cpf + var cpf: CustomProfileField? + if let fieldDictionary = field as? [String: AnyObject] { + cpf = CustomProfileField(field: fieldDictionary) } else { - if let cpf = CustomProfileField(id: field as? String), id = cpf.id { - fields[id] = cpf - } + cpf = CustomProfileField(id: field as? String) } + if let id = cpf?.id { fields[id] = cpf } } } } - internal init?(customFields: [String: AnyObject]?) { + internal init(customFields: [String: AnyObject]?) { if let customFields = customFields { for key in customFields.keys { - if let cpf = CustomProfileField(field: customFields[key] as? [String: AnyObject]) { - self.fields[key] = cpf - } + let cpf = CustomProfileField(field: customFields[key] as? [String: AnyObject]) + self.fields[key] = cpf } } } @@ -241,7 +232,7 @@ public struct CustomProfileField { internal(set) public var possibleValues: [String]? internal(set) public var type: String? - internal init?(field: [String: AnyObject]?) { + internal init(field: [String: AnyObject]?) { id = field?["id"] as? String alt = field?["alt"] as? String value = field?["value"] as? String @@ -254,7 +245,7 @@ public struct CustomProfileField { type = field?["type"] as? String } - internal init?(id: String?) { + internal init(id: String?) { self.id = id } diff --git a/SlackKit/Sources/User.swift b/SlackKit/Sources/User.swift index dbbd8c5..1337674 100644 --- a/SlackKit/Sources/User.swift +++ b/SlackKit/Sources/User.swift @@ -37,7 +37,7 @@ public struct User { internal(set) public var image192: String? internal(set) public var customProfile: CustomProfile? - internal init?(profile: [String: AnyObject]?) { + internal init(profile: [String: AnyObject]?) { firstName = profile?["first_name"] as? String lastName = profile?["last_name"] as? String realName = profile?["real_name"] as? String @@ -77,7 +77,7 @@ public struct User { // Client properties internal(set) public var userGroups: [String: String]? - internal init?(user: [String: AnyObject]?) { + internal init(user: [String: AnyObject]?) { id = user?["id"] as? String name = user?["name"] as? String deleted = user?["deleted"] as? Bool @@ -99,7 +99,7 @@ public struct User { preferences = user?["prefs"] as? [String: AnyObject] } - internal init?(id: String?) { + internal init(id: String?) { self.id = id self.isBot = nil } diff --git a/SlackKit/Sources/UserGroup.swift b/SlackKit/Sources/UserGroup.swift index ef03261..c9d5307 100644 --- a/SlackKit/Sources/UserGroup.swift +++ b/SlackKit/Sources/UserGroup.swift @@ -43,7 +43,7 @@ public struct UserGroup { internal(set) public var users: [String]? internal(set) public var userCount: Int? - internal init?(userGroup: [String: AnyObject]?) { + internal init(userGroup: [String: AnyObject]?) { id = userGroup?["id"] as? String teamID = userGroup?["team_id"] as? String isUserGroup = userGroup?["is_usergroup"] as? Bool diff --git a/SlackKit/Supporting Files/Info-iOS.plist b/SlackKit/Supporting Files/Info-iOS.plist index b90b6ec..ffc1d2e 100644 --- a/SlackKit/Supporting Files/Info-iOS.plist +++ b/SlackKit/Supporting Files/Info-iOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0.3 + 1.1.0 CFBundleSignature ???? CFBundleVersion diff --git a/SlackKit/Supporting Files/Info-tvOS.plist b/SlackKit/Supporting Files/Info-tvOS.plist index b90b6ec..ffc1d2e 100644 --- a/SlackKit/Supporting Files/Info-tvOS.plist +++ b/SlackKit/Supporting Files/Info-tvOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0.3 + 1.1.0 CFBundleSignature ???? CFBundleVersion diff --git a/SlackKit/Supporting Files/Info.plist b/SlackKit/Supporting Files/Info.plist index b90b6ec..ffc1d2e 100644 --- a/SlackKit/Supporting Files/Info.plist +++ b/SlackKit/Supporting Files/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0.3 + 1.1.0 CFBundleSignature ???? CFBundleVersion