Compare commits

...

106 Commits

Author SHA1 Message Date
Peter Zignego d60b8bf88a Comment out disconnect functionality
Underlying Zewo/WebSocket bug
2016-05-17 21:36:05 -04:00
Peter Zignego f2f95b7a26 Fix typo 2016-05-17 21:11:13 -04:00
Peter Zignego c4b38dbc14 Update readme 2016-05-17 20:17:28 -04:00
Peter Zignego 7fffbc8dba Weak self 2016-05-17 15:49:57 -04:00
Peter Zignego ab39fda0f5 Use Venice for async 2016-05-17 14:41:18 -04:00
Peter Zignego 11e9f2e9ab Fix random boundary 2016-05-17 13:19:50 -04:00
Peter Zignego a93b4b7966 Code improvements 2016-05-17 11:51:54 -04:00
Peter Zignego b639fff78e Add examples 2016-05-09 09:15:38 -04:00
Peter Zignego 6075577ec5 Update readme 2016-05-09 00:39:48 -04:00
Peter Zignego 7a967b8f7d Remove legacy header files 2016-05-08 23:31:14 -04:00
Peter Zignego 5f53f89693 Handle Bools properly 2016-05-08 23:29:18 -04:00
Peter Zignego 30d701f6de Remove project file from scm 2016-05-08 00:05:50 -04:00
Peter Zignego 82e200f4cc Fix gitignore 2016-05-08 00:04:22 -04:00
Peter Zignego 6cca5c3956 Ignore project file 2016-05-08 00:02:34 -04:00
Peter Zignego 43f184a8ed Bug fixes 2016-05-08 00:00:22 -04:00
Peter Zignego c5ce209e54 Bump dependency versions 2016-05-07 16:38:05 -04:00
Peter Zignego fc336cd2ee Updates 2016-05-07 16:37:50 -04:00
Peter Zignego 7138cf3f4f Update readme 2016-05-05 15:01:15 -04:00
Peter Zignego 00b2605a37 Add public contains extension 2016-05-05 13:14:48 -04:00
Peter Zignego 2561e8ba48 Bug fix 2016-05-04 23:31:02 -04:00
Peter Zignego 00b0823dbd Percent encoding bug fix 2016-05-04 23:25:19 -04:00
Peter Zignego b88ef3638c Project file updates 2016-05-04 20:37:09 -04:00
Peter Zignego 1675aa82fb Use foundation for percent encoding for now 2016-05-04 15:04:25 -04:00
Peter Zignego 01be45e979 Fixes 2016-05-03 19:15:37 -04:00
Peter Zignego dc3336a807 Percent encoding fix 2016-05-03 00:41:28 -04:00
Peter Zignego 5962ce5115 Fixes 2016-05-02 21:22:47 -04:00
Peter Zignego db6bf52eaa Update dependencies 2016-05-02 17:57:06 -04:00
Peter Zignego 32ae1ed7a2 Remove foundation dependencies 2016-05-02 17:56:35 -04:00
Peter Zignego 2e4b4390b3 Zewo implementation 2016-05-02 13:40:53 -04:00
Peter Zignego 07861887f1 Start Zewo implementation 2016-05-02 11:19:27 -04:00
Peter Zignego 1c9b7bb011 Remove example 2016-05-02 11:19:13 -04:00
Peter Zignego 59e1e8856d Swift 3 renaming 2016-05-02 10:36:11 -04:00
Peter Zignego 76b33dba0e Setup 2016-05-02 10:35:56 -04:00
Peter Zignego 6040d8deaa Clean up 2016-05-02 10:35:38 -04:00
Peter Zignego b2be8d0170 Merge pull request #24 from pvzig/1.0.1
v1.0.1
2016-04-09 11:40:17 -04:00
Peter Zignego 45be1b7a3f Bump version number 2016-04-09 11:33:22 -04:00
Peter Zignego 09aa72d43e Update readme to include explanation of Leaderboard example 2016-04-09 11:24:47 -04:00
Peter Zignego bf4b55bbd6 Add leaderboard bot example 2016-04-09 11:02:30 -04:00
Peter Zignego a82279fad1 Code cleanup and improvement 2016-04-04 23:49:25 -04:00
Peter Zignego 500e489d5d Add AttachmentField type and AttachmentColor enum 2016-04-04 21:26:09 -04:00
Peter Zignego 874f4f51e1 Fix send message optional parameter handling 2016-04-03 21:04:49 -04:00
Peter Zignego 654f419f4e Support for rtm.start parameters 2016-04-03 15:45:53 -04:00
Peter Zignego a7c25fe33b Version bump 2016-03-22 21:55:26 -04:00
Peter Zignego d2037f4cc5 Merge pull request #23 from pvzig/feature/client-improvements
Feature/client-improvements
2016-03-22 21:44:20 -04:00
Peter Zignego b87232dfb5 Readme updates 2016-03-22 21:38:17 -04:00
Peter Zignego 9e5678739f Update readme 2016-03-22 21:34:45 -04:00
Peter Zignego 5cc8582d65 Code cleanup 2016-03-22 21:29:16 -04:00
Peter Zignego aa078934c0 File comment web api errors 2016-03-22 20:56:10 -04:00
Peter Zignego 5c157caea3 Client improvements - disconnect, ping-pong, timeout, reconnect 2016-03-22 20:50:29 -04:00
Peter Zignego b567113b5f Update Starscream dependency version 2016-03-22 20:44:58 -04:00
Peter Zignego 5b08fb6031 Merge pull request #21 from pvzig/feature/file-comment
Add support for file comment web API calls
2016-03-19 14:21:52 -04:00
Peter Zignego b48f33fb72 Add support for file comment web API calls 2016-03-19 14:12:04 -04:00
Peter Zignego deccb727a1 Merge pull request #20 from pvzig/feature/reactions-itemUser
Add support for the item user reaction property
2016-03-15 23:48:55 -04:00
Peter Zignego 8f1df8d138 Add support for the itemUser reaction property 2016-03-15 23:48:08 -04:00
Peter Zignego 0048710e24 Merge pull request #19 from pvzig/feature/dnd-read
DND Read Scope Web API implementation
2016-03-15 23:38:08 -04:00
Peter Zignego f6da0ddd32 DND read scope fixes 2016-03-15 23:37:33 -04:00
Peter Zignego 513485e704 DND Read Scope Web API implementation 2016-03-15 23:28:38 -04:00
Peter Zignego fb3719c29d Merge pull request #18 from pvzig/feature/rtm-team-profile
Add support for team profile events
2016-03-15 22:48:43 -04:00
Peter Zignego 76fdc55f9e Team profile events 2016-03-15 22:45:45 -04:00
Peter Zignego 687b57fc1f Version bump 2016-03-01 23:08:24 -05:00
Peter Zignego 319dc9a095 Merge pull request #17 from muratayusuke/bugfix/ignore_int_param
Bugfix: Do not ignore Int and Bool parameters when making requests to the web api
2016-03-01 12:47:20 -05:00
muratayusuke 64440b5c5b Do not ignore Int/Bool param such as count 2016-03-02 02:25:37 +09:00
Peter Zignego 3efe5752cb Merge pull request #16 from pvzig/feature/history-object
Feature/history object
2016-02-28 12:41:22 -05:00
Peter Zignego 31f69e3afd Move History struct to Types.swift, minor changes 2016-02-28 12:40:04 -05:00
muratayusuke 72b29562e5 Introduce history type 2016-02-28 12:29:12 -05:00
Peter Zignego 2b26eb35d7 Merge pull request #14 from pvzig/fix/team-object
fix/team-object
2016-02-27 14:13:53 -05:00
Peter Zignego c4c8e99c0b Bug fixes, team icon support 2016-02-27 14:10:35 -05:00
muratayusuke 5efe222196 Add team icon 2016-02-27 14:06:24 -05:00
Peter Zignego e56ff971b2 Merge pull request #12 from hamin/public-message-initializer
Making Message struct initializer public.
2016-02-27 13:48:42 -05:00
Haris Amin 18b8a31d85 Making Message struct initializer public. 2016-02-27 13:38:52 -05:00
Peter Zignego d386730c75 Project file update 2016-02-21 10:31:13 -05:00
Peter Zignego bc6e94d99a Merge pull request #11 from pvzig/feature/message-attachments
Message attachment support
2016-02-21 10:27:41 -05:00
Peter Zignego 89f5e3786e Message attachment support 2016-02-21 10:18:32 -05:00
Peter Zignego 0665ea0270 Add sample app 2016-02-18 18:21:17 -05:00
Peter Zignego 4ebc33b59a Update podspec, project, info.plist 2016-02-18 13:38:47 -05:00
Peter Zignego 9e93806437 Merge pull request #10 from pvzig/web-api
Add bot accessible Slack Web API endpoints
2016-02-18 13:29:34 -05:00
Peter Zignego 572e363717 Typo fix 2016-02-18 13:27:24 -05:00
Peter Zignego d8c8c3cb57 Updates 2016-02-18 13:24:14 -05:00
Peter Zignego 02eff541b1 Fixes 2016-02-18 12:57:53 -05:00
Peter Zignego 92d6d833ea Clean up 2016-02-15 20:13:10 -05:00
Peter Zignego 78b73c4c99 Bug fixes 2016-02-14 21:31:42 -05:00
Peter Zignego dee376e1b9 Make success and failure closures optional 2016-02-14 21:09:07 -05:00
Peter Zignego 5550658d83 Encode parameters 2016-02-14 21:08:51 -05:00
Peter Zignego 82978bb963 Add additional errors 2016-02-14 20:35:03 -05:00
Peter Zignego 0a203bbc68 Web api clean up 2016-02-14 20:34:41 -05:00
Peter Zignego 38a762c60b File upload 2016-02-14 19:44:05 -05:00
Peter Zignego 96543932af General clean up 2016-02-14 16:35:39 -05:00
Peter Zignego 39fa80904f WebAPI interface clean up 2016-02-14 16:35:19 -05:00
Peter Zignego e4f0429ffb Send message implementation 2016-02-14 14:15:04 -05:00
Peter Zignego ab41c148cd Make Slack message formatting a string extension 2016-02-14 14:14:37 -05:00
Peter Zignego a74aba3c5e Remove depreciated parameters 2016-02-14 14:14:06 -05:00
Peter Zignego 9f266fff64 WebAPI clean up 2016-02-14 12:26:53 -05:00
Peter Zignego 78d31af3f7 Move client code out of SlackWebAPI 2016-02-14 12:04:09 -05:00
Peter Zignego 1a787019ec Add error handling 2016-02-14 11:16:07 -05:00
Peter Zignego f2f25763e7 Move source file to Sources 2016-02-14 10:16:07 -05:00
Peter Zignego de3cf52687 Web API partial implementation
Need to update with error closures across all methods and client integration
2016-02-13 16:04:14 -05:00
Peter Zignego 49b151cd98 Merge branch 'master' into web-api
# Conflicts:
#	SlackKit/Sources/Client.swift
2016-02-11 18:42:11 -05:00
Peter Zignego e9fc66a68f Merge pull request #9 from muratayusuke/feature/multi_clients
Support for multiple client instances
2016-02-11 18:20:42 -05:00
muratayusuke 0e25584191 Work with many Client instances 2016-02-12 02:11:15 +09:00
Peter Zignego 074be89825 Web API scaffolding 2016-02-08 18:38:23 -05:00
Peter Zignego 12dcb791a8 Error handling scaffolding 2016-02-08 18:37:39 -05:00
Peter Zignego de7dbfb9df API interface improvements 2016-02-08 18:37:26 -05:00
Peter Zignego fe49ca7b25 Update podspec 2016-02-02 09:30:40 -05:00
Peter Zignego 02791a4ea0 Move into sources folder 2016-01-18 20:19:33 -05:00
Peter Zignego 64a0b0e8dc Fix networking 2016-01-18 20:11:33 -05:00
Peter Zignego 6653d934f6 Start implementing the Slack Web API 2016-01-18 17:09:31 -05:00
36 changed files with 3027 additions and 1527 deletions
+1
View File
@@ -18,6 +18,7 @@ DerivedData
*.xcuserstate
.build
Packages/
*.xcodeproj/
# CocoaPods
#
+8
View File
@@ -0,0 +1,8 @@
import PackageDescription
let package = Package(
name: "leaderboard",
dependencies: [
.Package(url: "https://github.com/pvzig/SlackKit.git", majorVersion: 0, minor: 0),
]
)
+167
View File
@@ -0,0 +1,167 @@
//
// main.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import String
import SlackKit
class Leaderboard: MessageEventsDelegate {
var leaderboard: [String: Int] = [String: Int]()
let atSet = CharacterSet(characters: ["@"])
let client: SlackClient
init(token: String) {
client = SlackClient(apiToken: token)
client.messageEventsDelegate = self
}
enum Command: String {
case Leaderboard = "leaderboard"
}
enum Trigger: String {
case PlusPlus = "++"
case MinusMinus = "--"
}
// MARK: MessageEventsDelegate
func messageReceived(message: Message) {
listen(message: message)
}
func messageSent(message: Message){}
func messageChanged(message: Message){}
func messageDeleted(message: Message?){}
// MARK: Leaderboard Internal Logic
private func listen(message: Message) {
if let id = client.authenticatedUser?.id, text = message.text {
if text.lowercased().contains(query: Command.Leaderboard.rawValue) && text.contains(query: id) {
handleCommand(command: .Leaderboard, channel: message.channel)
}
}
if message.text?.contains(query: Trigger.PlusPlus.rawValue) == true {
handleMessageWithTrigger(message: message, trigger: .PlusPlus)
}
if message.text?.contains(query: Trigger.MinusMinus.rawValue) == true {
handleMessageWithTrigger(message: message, trigger: .MinusMinus)
}
}
private func handleMessageWithTrigger(message: Message, trigger: Trigger) {
if let text = message.text,
start = text.index(of: "@"),
end = text.index(of: trigger.rawValue) {
let string = String(text.characters[start...end].dropLast().dropFirst())
let users = client.users.values.filter{$0.id == self.userID(string: string)}
if users.count > 0 {
let idString = userID(string: string)
initalizationForValue(dictionary: &leaderboard, value: idString)
scoringForValue(dictionary: &leaderboard, value: idString, trigger: trigger)
} else {
initalizationForValue(dictionary: &leaderboard, value: string)
scoringForValue(dictionary: &leaderboard, value: string, trigger: trigger)
}
}
}
private func handleCommand(command: Command, channel:String?) {
switch command {
case .Leaderboard:
if let id = channel {
client.webAPI.sendMessage(channel:id, text: "Leaderboard", linkNames: true, attachments: [constructLeaderboardAttachment()], success: {(response) in
}, failure: { (error) in
print("Leaderboard failed to post due to error:\(error)")
})
}
}
}
private func initalizationForValue( dictionary: inout [String: Int], value: String) {
if dictionary[value] == nil {
dictionary[value] = 0
}
}
private func scoringForValue( dictionary: inout [String: Int], value: String, trigger: Trigger) {
switch trigger {
case .PlusPlus:
dictionary[value]?+=1
case .MinusMinus:
dictionary[value]?-=1
}
}
// MARK: Leaderboard Interface
private func constructLeaderboardAttachment() -> Attachment? {
let 💯 = AttachmentField(title: "💯", value: swapIDsForNames(string: topItems(dictionary: &leaderboard)), short: true)
let 💩 = AttachmentField(title: "💩", value: swapIDsForNames(string: bottomItems(dictionary: &leaderboard)), short: true)
return Attachment(fallback: "Leaderboard", title: "Leaderboard", colorHex: AttachmentColor.Good.rawValue, text: "", fields: [💯, 💩])
}
private func topItems(dictionary: inout [String: Int]) -> String {
let sortedKeys = dictionary.keys.sorted(isOrderedBefore: ({dictionary[$0] > dictionary[$1]})).filter({dictionary[$0] > 0})
let sortedValues = dictionary.values.sorted(isOrderedBefore: {$0 > $1}).filter({$0 > 0})
return leaderboardString(keys: sortedKeys, values: sortedValues)
}
private func bottomItems( dictionary: inout [String: Int]) -> String {
let sortedKeys = dictionary.keys.sorted(isOrderedBefore: ({dictionary[$0] < dictionary[$1]})).filter({dictionary[$0] < 0})
let sortedValues = dictionary.values.sorted(isOrderedBefore: {$0 < $1}).filter({$0 < 0})
return leaderboardString(keys: sortedKeys, values: sortedValues)
}
private func leaderboardString(keys: [String], values: [Int]) -> String {
var returnValue = ""
for i in 0..<values.count {
returnValue += keys[i] + " (" + "\(values[i])" + ")\n"
}
return returnValue
}
// MARK: - Utilities
private func swapIDsForNames(string: String) -> String {
var returnString = string
for key in client.users.keys {
if let name = client.users[key]?.name {
if returnString.contains(query: key) {
returnString.replace(string: key, with: "@"+name)
}
}
}
return returnString
}
private func userID(string: String) -> String {
let alphanumericSet = CharacterSet(characters:
["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
"0","1","2","3","4","5","6","7","8","9"])
return string.trim(alphanumericSet.inverted)
}
}
let leaderboard = Leaderboard(token: "xoxb-SLACK_API_TOKEN")
leaderboard.client.connect()
+9
View File
@@ -0,0 +1,9 @@
import PackageDescription
let package = Package(
name: "robot-or-not-bot",
targets: [],
dependencies: [
.Package(url: "https://github.com/pvzig/SlackKit.git", majorVersion: 0, minor: 0),
]
)
@@ -0,0 +1,139 @@
//
// main.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import SlackKit
class RobotOrNotBot: MessageEventsDelegate {
let verdicts: [String:Bool] = [
"Mr. Roboto" : false,
"Service Kiosks": false,
"Darth Vader": false,
"K-9": true,
"Emotions": false,
"Self-Driving Cars": false,
"Telepresence Robots": false,
"Roomba": true,
"Assembly-Line Robot": false,
"ASIMO": false,
"KITT": false,
"USS Enterprise": false,
"Transformers": true,
"Jaegers": false,
"The Major": false,
"Siri": false,
"The Terminator": true,
"Commander Data": false,
"Marvin the Paranoid Android": true,
"Pinocchio": false,
"Droids": true,
"Hitchbot": false,
"Mars Rovers": false,
"Space Probes": false,
"Sasquatch": false,
"Toaster": false,
"Toaster Oven": false,
"Cylons": false,
"V'ger": true,
"Ilia Robot": false,
"The TARDIS": false,
"Johnny 5": true,
"Twiki": true,
"Dr. Theopolis": false,
"robots.txt": false,
"Lobot": false,
"Vicki": true,
"GlaDOS": false,
"Turrets": true,
"Wheatley": true,
"Herbie the Love Bug": false,
"Iron Man": false,
"Ultron": false,
"The Vision": false,
"Clockwork Droids": false,
"Podcasts": false,
"Cars": false,
"Swimming Pool Cleaners": false,
"Burritos": false,
"Prince Robot IV": false,
"Daleks": false,
"Cybermen": false,
"The Internet of Things": false,
"Nanobots": true,
"Two Intermeshed Gears": false,
"Crow T. Robot": true,
"Tom Servo": true,
"Thomas and Friends": false,
"Replicants": false,
"Chatbots": false,
"Agents": false,
"Lego Simulated Worm Toy": true,
"Ghosts": false,
"Exos": true,
"Rasputin": false,
"Tamagotchi": false,
"T-1000": true,
"The Tin Woodman": false,
"Mic N. The Robot": true,
"Robot Or Not Bot": false
]
let client: SlackClient
init(token: String) {
client = SlackClient(apiToken: token)
client.messageEventsDelegate = self
}
// MARK: MessageEventsDelegate
func messageReceived(message: Message) {
if let id = client.authenticatedUser?.id {
if message.text?.contains(query: id) == true {
handleMessage(message: message)
}
}
}
func messageSent(message: Message){}
func messageChanged(message: Message){}
func messageDeleted(message: Message?){}
private func handleMessage(message: Message) {
if let text = message.text?.lowercased(), channel = message.channel {
for (robot, verdict) in verdicts {
let lowerbot = robot.lowercased()
if text.contains(query: lowerbot) {
if verdict == true {
client.webAPI.addReaction(name: "robot_face", timestamp: message.ts, channel: channel, success: nil, failure: nil)
} else {
client.webAPI.addReaction(name: "no_entry_sign", timestamp: message.ts, channel: channel, success: nil, failure: nil)
}
return
}
}
client.webAPI.addReaction(name: "question", timestamp: message.ts, channel: channel, success: nil, failure: nil)
}
}
}
let slackbot = RobotOrNotBot(token: "xoxb-SLACK_API_TOKEN")
slackbot.client.connect()
+5 -3
View File
@@ -27,7 +27,9 @@ let package = Package(
name: "SlackKit",
targets: [],
dependencies: [
.Package(url: "https://github.com/pvzig/Starscream.git",
majorVersion: 1),
]
.Package(url: "https://github.com/open-swift/C7.git", majorVersion: 0, minor: 7),
.Package(url: "https://github.com/czechboy0/Jay.git", majorVersion: 0, minor: 6),
.Package(url: "https://github.com/Zewo/WebSocket", majorVersion: 0, minor: 6),
],
exclude: ["Examples"]
)
-7
View File
@@ -1,7 +0,0 @@
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
target 'SlackKit' do
pod 'Starscream'
end
-10
View File
@@ -1,10 +0,0 @@
PODS:
- Starscream (1.0.2)
DEPENDENCIES:
- Starscream
SPEC CHECKSUMS:
Starscream: 40e2c4c1c770d811f24116b8b7dbeb4542c56767
COCOAPODS: 0.39.0
+103 -30
View File
@@ -1,10 +1,13 @@
![SlackKit](https://cloud.githubusercontent.com/assets/8311605/10260893/5ec60f96-694e-11e5-91fd-da6845942201.png)
##iOS/OS X Slack Client Library
##Alpha Linux Slack Client Library
###Description
This is a Slack client library for iOS and OS X written in Swift. It's intended to expose all of the functionality of Slack's [Real Time Messaging API](https://api.slack.com/rtm).
This is a Slack client library for Linux 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).
###Disclaimer: The linux version of SlackKit is a pre-release alpha. Feel free to report issues you come across.
###Installation
####Swift Package Manager (Swift 2.2 and up)
####Swift Package Manager
Add SlackKit to your Package.swift
```swift
@@ -12,42 +15,124 @@ import PackageDescription
let package = Package(
dependencies: [
.Package(url: "https://github.com/pvzig/SlackKit.git", majorVersion: 0)
.Package(url: "https://github.com/pvzig/SlackKit.git", majorVersion: 0, minor: 0)
]
)
```
Run `swift-build` on your applications main directory.
####Development
1. Install Homebrew: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
2. Install `swiftenv`: `brew install kylef/formulae/swiftenv`
3. Configure your shell: `echo 'if which swiftenv > /dev/null; then eval "$(swiftenv init -)"; fi' >> ~/.bash_profile`
4. Download and install the latest Zewo compatible snapshot:
```
swiftenv install DEVELOPMENT-SNAPSHOT-2016-05-03-a
swiftenv local DEVELOPMENT-SNAPSHOT-2016-05-03-a
```
5. Install and Link OpenSSL: `brew install openssl`, `brew link openssl --force`
####CocoaPods
Add the pod to your podfile:
To build an application that uses SlackKit in Xcode, simply use SwiftPM. (For the 05-03 snapshot you must run `swift build` before generating an Xcode project:
```
pod 'SlackKit'
```
and run
```
pod install
swift build
swift build -Xlinker -L$(pwd)/.build/debug/ -Xswiftc -I/usr/local/include -Xlinker -L/usr/local/lib -X
```
To use the library in your project import it:
```
import SlackKit
```
####Deployment
Deploy your application to Heroku using [this buildpack](https://github.com/pvzig/heroku-buildpack-swift). For more detailed instructions please see [this post](https://medium.com/@pvzig/building-slack-bots-in-swift-b99e243e444c).
###Usage
To use SlackKit you'll need a bearer token which identifies a single user. You can generate a [full access token or create one using OAuth 2](https://api.slack.com/web).
Once you have a token, give it to the Client:
Once you have a token, initialize a client instance using it:
```swift
Client.sharedInstance.setAuthToken("YOUR_SLACK_AUTH_TOKEN")
let client = Client(apiToken: "YOUR_SLACK_API_TOKEN")
```
and connect:
If you want to receive messages from the Slack RTM API, connect to it.
```swift
Client.sharedInstance.connect()
client.connect()
```
Once connected, the client will begin to consume any messages sent by the Slack RTM API.
####Web API Methods
SlackKit currently supports the a subset of the Slack Web APIs that are available to bot users:
- api.test
- auth.test
- channels.history
- channels.info
- channels.list
- channels.mark
- channels.setPurpose
- channels.setTopic
- chat.delete
- chat.postMessage
- chat.update
- emoji.list
- files.comments.add
- files.comments.edit
- files.comments.delete
- files.delete
- files.upload
- groups.close
- groups.history
- groups.info
- groups.list
- groups.mark
- groups.open
- groups.setPurpose
- groups.setTopic
- im.close
- im.history
- im.list
- im.mark
- im.open
- mpim.close
- mpim.history
- mpim.list
- mpim.mark
- mpim.open
- pins.add
- pins.list
- pins.remove
- reactions.add
- reactions.get
- reactions.list
- reactions.remove
- rtm.start
- stars.add
- stars.remove
- team.info
- users.getPresence
- users.info
- users.list
- users.setActive
- users.setPresence
They can be accessed through a Client objects `webAPI` property:
```swift
client.webAPI.authenticationTest({
(authenticated) -> Void in
print(authenticated)
}){(error) -> Void in
print(error)
}
```
####Delegate methods
To receive delegate callbacks for certain events, register an object as the delegate for those events:
```swift
client.slackEventsDelegate = self
```
There are a number of delegates that you can set to receive callbacks for certain events.
#####SlackEventsDelegate
@@ -116,8 +201,8 @@ func itemStarred(item: Item, star: Bool)
#####ReactionEventsDelegate
```swift
func reactionAdded(reaction: String?, item: Item?)
func reactionRemoved(reaction: String?, item: Item?)
func reactionAdded(reaction: String?, item: Item?, itemUser: String?)
func reactionRemoved(reaction: String?, item: Item?, itemUser: String?)
```
#####TeamEventsDelegate
@@ -138,18 +223,6 @@ func subteamSelfAdded(subteamID: String)
func subteamSelfRemoved(subteamID: String)
```
###Examples
####Sending a Message:
```swift
Client.sharedInstance.sendMessage(message: "Hello, world!", channelID: "CHANNEL_ID")
```
####Print a List of Users in a Channel:
```swift
let users = Client.sharedInstance.channels?["CHANNEL_ID"]?.members
print(users)
```
###Get In Touch
[@pvzig](https://twitter.com/pvzig)
-30
View File
@@ -1,30 +0,0 @@
//
// SlackKit.h
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
//! Project version number for SlackKit.
FOUNDATION_EXPORT double SlackKitVersionNumber;
//! Project version string for SlackKit.
FOUNDATION_EXPORT const unsigned char SlackKitVersionString[];
-16
View File
@@ -1,16 +0,0 @@
Pod::Spec.new do |s|
s.name = "SlackKit"
s.version = "0.9.6"
s.summary = "a Slack client library for iOS and OS X written in Swift"
s.homepage = "https://github.com/pvzig/SlackKit"
s.license = 'MIT'
s.author = { "Peter Zignego" => "peter@launchsoft.co" }
s.source = { :git => "https://github.com/pvzig/SlackKit.git", :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/pvzig'
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.9'
s.requires_arc = true
s.source_files = 'SlackKit/Sources/*.swift'
s.frameworks = 'Foundation'
s.dependency 'Starscream', '~> 1.0.2'
end
-398
View File
@@ -1,398 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
26BBA1941C398E3C00BF7225 /* Bot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1871C398E3C00BF7225 /* Bot.swift */; };
26BBA1951C398E3C00BF7225 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1881C398E3C00BF7225 /* Channel.swift */; };
26BBA1961C398E3C00BF7225 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1891C398E3C00BF7225 /* Client.swift */; };
26BBA1971C398E3C00BF7225 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA18A1C398E3C00BF7225 /* Event.swift */; };
26BBA1981C398E3C00BF7225 /* EventDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA18B1C398E3C00BF7225 /* EventDelegate.swift */; };
26BBA1991C398E3C00BF7225 /* EventDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA18C1C398E3C00BF7225 /* EventDispatcher.swift */; };
26BBA19A1C398E3C00BF7225 /* EventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA18D1C398E3C00BF7225 /* EventHandler.swift */; };
26BBA19B1C398E3C00BF7225 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA18E1C398E3C00BF7225 /* File.swift */; };
26BBA19C1C398E3C00BF7225 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA18F1C398E3C00BF7225 /* Message.swift */; };
26BBA19D1C398E3C00BF7225 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1901C398E3C00BF7225 /* Team.swift */; };
26BBA19E1C398E3C00BF7225 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1911C398E3C00BF7225 /* Types.swift */; };
26BBA19F1C398E3C00BF7225 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1921C398E3C00BF7225 /* User.swift */; };
26BBA1A01C398E3C00BF7225 /* UserGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BBA1931C398E3C00BF7225 /* UserGroup.swift */; };
FFE3AC870D1C42EF276CCA2D /* Pods_SlackKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 407A2ABFC0611867E2BE34D0 /* Pods_SlackKit.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
26072A341BB48B3A00CD650C /* SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2661A6A41BBF62FF0026F67B /* SlackKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SlackKit.h; sourceTree = "<group>"; };
266E05F01BBF780C00840D76 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
26BBA1871C398E3C00BF7225 /* Bot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bot.swift; path = Sources/Bot.swift; sourceTree = "<group>"; };
26BBA1881C398E3C00BF7225 /* Channel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Channel.swift; path = Sources/Channel.swift; sourceTree = "<group>"; };
26BBA1891C398E3C00BF7225 /* Client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Client.swift; path = Sources/Client.swift; sourceTree = "<group>"; };
26BBA18A1C398E3C00BF7225 /* Event.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Event.swift; path = Sources/Event.swift; sourceTree = "<group>"; };
26BBA18B1C398E3C00BF7225 /* EventDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EventDelegate.swift; path = Sources/EventDelegate.swift; sourceTree = "<group>"; };
26BBA18C1C398E3C00BF7225 /* EventDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EventDispatcher.swift; path = Sources/EventDispatcher.swift; sourceTree = "<group>"; };
26BBA18D1C398E3C00BF7225 /* EventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EventHandler.swift; path = Sources/EventHandler.swift; sourceTree = "<group>"; };
26BBA18E1C398E3C00BF7225 /* File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = File.swift; path = Sources/File.swift; sourceTree = "<group>"; };
26BBA18F1C398E3C00BF7225 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Message.swift; path = Sources/Message.swift; sourceTree = "<group>"; };
26BBA1901C398E3C00BF7225 /* Team.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Team.swift; path = Sources/Team.swift; sourceTree = "<group>"; };
26BBA1911C398E3C00BF7225 /* Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Types.swift; path = Sources/Types.swift; sourceTree = "<group>"; };
26BBA1921C398E3C00BF7225 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = User.swift; path = Sources/User.swift; sourceTree = "<group>"; };
26BBA1931C398E3C00BF7225 /* UserGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UserGroup.swift; path = Sources/UserGroup.swift; sourceTree = "<group>"; };
407A2ABFC0611867E2BE34D0 /* Pods_SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4347F92F3932C96C23B10B2A /* Pods-SlackKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SlackKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-SlackKit/Pods-SlackKit.release.xcconfig"; sourceTree = "<group>"; };
F59B6A12F1C4C4E24C58E1BF /* Pods-SlackKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SlackKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SlackKit/Pods-SlackKit.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
26072A301BB48B3A00CD650C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
FFE3AC870D1C42EF276CCA2D /* Pods_SlackKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0C2928E508A686B69F9F0117 /* Pods */ = {
isa = PBXGroup;
children = (
F59B6A12F1C4C4E24C58E1BF /* Pods-SlackKit.debug.xcconfig */,
4347F92F3932C96C23B10B2A /* Pods-SlackKit.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
26072A2A1BB48B3A00CD650C = {
isa = PBXGroup;
children = (
2661A6811BBF60E60026F67B /* SlackKit */,
26072A351BB48B3A00CD650C /* Products */,
0C2928E508A686B69F9F0117 /* Pods */,
CA70A3A1A9A1A259960DFBCF /* Frameworks */,
);
sourceTree = "<group>";
};
26072A351BB48B3A00CD650C /* Products */ = {
isa = PBXGroup;
children = (
26072A341BB48B3A00CD650C /* SlackKit.framework */,
);
name = Products;
sourceTree = "<group>";
};
2661A6811BBF60E60026F67B /* SlackKit */ = {
isa = PBXGroup;
children = (
26BBA1871C398E3C00BF7225 /* Bot.swift */,
26BBA1881C398E3C00BF7225 /* Channel.swift */,
26BBA1891C398E3C00BF7225 /* Client.swift */,
26BBA18A1C398E3C00BF7225 /* Event.swift */,
26BBA18B1C398E3C00BF7225 /* EventDelegate.swift */,
26BBA18C1C398E3C00BF7225 /* EventDispatcher.swift */,
26BBA18D1C398E3C00BF7225 /* EventHandler.swift */,
26BBA18E1C398E3C00BF7225 /* File.swift */,
26BBA18F1C398E3C00BF7225 /* Message.swift */,
26BBA1901C398E3C00BF7225 /* Team.swift */,
26BBA1911C398E3C00BF7225 /* Types.swift */,
26BBA1921C398E3C00BF7225 /* User.swift */,
26BBA1931C398E3C00BF7225 /* UserGroup.swift */,
2661A6A41BBF62FF0026F67B /* SlackKit.h */,
266E05F01BBF780C00840D76 /* Info.plist */,
);
path = SlackKit;
sourceTree = "<group>";
};
CA70A3A1A9A1A259960DFBCF /* Frameworks */ = {
isa = PBXGroup;
children = (
407A2ABFC0611867E2BE34D0 /* Pods_SlackKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
26072A311BB48B3A00CD650C /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
26072A331BB48B3A00CD650C /* SlackKit */ = {
isa = PBXNativeTarget;
buildConfigurationList = 26072A3C1BB48B3B00CD650C /* Build configuration list for PBXNativeTarget "SlackKit" */;
buildPhases = (
EBD7A091EB278C5BA34791C5 /* Check Pods Manifest.lock */,
26072A2F1BB48B3A00CD650C /* Sources */,
26072A301BB48B3A00CD650C /* Frameworks */,
26072A311BB48B3A00CD650C /* Headers */,
26072A321BB48B3A00CD650C /* Resources */,
AC19A945F408269E4B4132CC /* Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = SlackKit;
productName = SlackRTMKit;
productReference = 26072A341BB48B3A00CD650C /* SlackKit.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
26072A2B1BB48B3A00CD650C /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "Launch Software LLC";
TargetAttributes = {
26072A331BB48B3A00CD650C = {
CreatedOnToolsVersion = 7.0;
};
};
};
buildConfigurationList = 26072A2E1BB48B3A00CD650C /* Build configuration list for PBXProject "SlackKit" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 26072A2A1BB48B3A00CD650C;
productRefGroup = 26072A351BB48B3A00CD650C /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
26072A331BB48B3A00CD650C /* SlackKit */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
26072A321BB48B3A00CD650C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
AC19A945F408269E4B4132CC /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SlackKit/Pods-SlackKit-resources.sh\"\n";
showEnvVarsInLog = 0;
};
EBD7A091EB278C5BA34791C5 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
26072A2F1BB48B3A00CD650C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
26BBA1991C398E3C00BF7225 /* EventDispatcher.swift in Sources */,
26BBA1951C398E3C00BF7225 /* Channel.swift in Sources */,
26BBA19F1C398E3C00BF7225 /* User.swift in Sources */,
26BBA19E1C398E3C00BF7225 /* Types.swift in Sources */,
26BBA1961C398E3C00BF7225 /* Client.swift in Sources */,
26BBA19A1C398E3C00BF7225 /* EventHandler.swift in Sources */,
26BBA1971C398E3C00BF7225 /* Event.swift in Sources */,
26BBA1941C398E3C00BF7225 /* Bot.swift in Sources */,
26BBA19B1C398E3C00BF7225 /* File.swift in Sources */,
26BBA19C1C398E3C00BF7225 /* Message.swift in Sources */,
26BBA19D1C398E3C00BF7225 /* Team.swift in Sources */,
26BBA1A01C398E3C00BF7225 /* UserGroup.swift in Sources */,
26BBA1981C398E3C00BF7225 /* EventDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
26072A3A1BB48B3B00CD650C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
26072A3B1BB48B3B00CD650C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
26072A3D1BB48B3B00CD650C /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F59B6A12F1C4C4E24C58E1BF /* Pods-SlackKit.debug.xcconfig */;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = SlackKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit;
PRODUCT_NAME = SlackKit;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
26072A3E1BB48B3B00CD650C /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4347F92F3932C96C23B10B2A /* Pods-SlackKit.release.xcconfig */;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = SlackKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit;
PRODUCT_NAME = SlackKit;
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
26072A2E1BB48B3A00CD650C /* Build configuration list for PBXProject "SlackKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
26072A3A1BB48B3B00CD650C /* Debug */,
26072A3B1BB48B3B00CD650C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
26072A3C1BB48B3B00CD650C /* Build configuration list for PBXNativeTarget "SlackKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
26072A3D1BB48B3B00CD650C /* Debug */,
26072A3E1BB48B3B00CD650C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 26072A2B1BB48B3A00CD650C /* Project object */;
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:SlackRTMKit.xcodeproj">
</FileRef>
</Workspace>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:SlackKit.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+132
View File
@@ -0,0 +1,132 @@
//
// Attachment.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct Attachment {
public let fallback: String?
public let color: String?
public let pretext: String?
public let authorName: String?
public let authorLink: String?
public let authorIcon: String?
public let title: String?
public let titleLink: String?
public let text: String?
public let fields: [AttachmentField]?
public let imageURL: String?
public let thumbURL: String?
internal init?(attachment: [String: Any]?) {
fallback = attachment?["fallback"] as? String
color = attachment?["color"] as? String
pretext = attachment?["pretext"] as? String
authorName = attachment?["author_name"] as? String
authorLink = attachment?["author_link"] as? String
authorIcon = attachment?["author_icon"] as? String
title = attachment?["title"] as? String
titleLink = attachment?["title_link"] as? String
text = attachment?["text"] as? String
imageURL = attachment?["image_url"] as? String
thumbURL = attachment?["thumb_url"] as? String
fields = (attachment?["fields"] as? [Any])?.objectArrayFromDictionaryArray(intializer: {(field) -> AttachmentField? in
return AttachmentField(field: field)
})
}
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
self.authorName = authorName
self.authorLink = authorLink
self.authorIcon = authorIcon
self.title = title
self.titleLink = titleLink
self.text = text
self.fields = fields
self.imageURL = imageURL
self.thumbURL = thumbURL
}
internal func dictionary() -> [String: Any] {
var attachment = [String: Any]()
attachment["fallback"] = fallback
attachment["color"] = color
attachment["pretext"] = pretext
attachment["authorName"] = authorName
attachment["author_link"] = authorLink
attachment["author_icon"] = authorIcon
attachment["title"] = title
attachment["title_link"] = titleLink
attachment["text"] = text
attachment["fields"] = fieldJSONArray(fields: fields)
attachment["image_url"] = imageURL
attachment["thumb_url"] = thumbURL
return attachment
}
private func fieldJSONArray(fields: [AttachmentField]?) -> [Any] {
var returnValue = [Any]()
if let f = fields {
for field in f {
returnValue.append(field.dictionary())
}
}
return returnValue
}
}
public struct AttachmentField {
public let title: String?
public let value: String?
public let short: Bool?
internal init?(field: [String: Any]?) {
title = field?["title"] as? String
value = field?["value"] as? String
short = field?["short"] as? Bool
}
public init(title:String, value:String, short: Bool? = nil) {
self.title = title
self.value = value.slackFormatEscaping()
self.short = short
}
internal func dictionary() -> [String: Any] {
var field = [String: Any]()
field["title"] = title
field["value"] = value
field["short"] = short
return field
}
}
public enum AttachmentColor: String {
case Good = "good"
case Warning = "warning"
case Danger = "danger"
}
+3 -3
View File
@@ -25,12 +25,12 @@ public struct Bot {
public let id: String?
internal(set) public var name: String?
internal(set) public var icons: [String: AnyObject]?
internal(set) public var icons: [String: Any]?
internal init?(bot: [String: AnyObject]?) {
internal init?(bot: [String: Any]?) {
id = bot?["id"] as? String
name = bot?["name"] as? String
icons = bot?["icons"] as? [String: AnyObject]
icons = bot?["icons"] as? [String: Any]
}
}
+9 -8
View File
@@ -43,13 +43,13 @@ public struct Channel {
internal(set) public var unread: Int?
internal(set) public var unreadCountDisplay: Int?
internal(set) public var hasPins: Bool?
internal(set) public var members = [String]()
internal(set) public var members: [String]?
// Client use
internal(set) public var pinnedItems = [Item]()
internal(set) public var usersTyping = [String]()
internal(set) public var messages = [String: Message]()
internal init?(channel: [String: AnyObject]?) {
internal init?(channel: [String: Any]?) {
id = channel?["id"] as? String
name = channel?["name"] as? String
created = channel?["created"] as? Int
@@ -62,19 +62,20 @@ public struct Channel {
isUserDeleted = channel?["is_user_deleted"] as? Bool
user = channel?["user"] as? String
isOpen = channel?["is_open"] as? Bool
topic = Topic(topic: channel?["topic"] as? [String: AnyObject])
purpose = Topic(topic: channel?["purpose"] as? [String: AnyObject])
topic = Topic(topic: channel?["topic"] as? [String: Any])
purpose = Topic(topic: channel?["purpose"] as? [String: Any])
isMember = channel?["is_member"] as? Bool
lastRead = channel?["last_read"] as? String
latest = Message(message: channel?["latest"] as? [String: AnyObject])
unread = channel?["unread_count"] as? Int
unreadCountDisplay = channel?["unread_count_display"] as? Int
hasPins = channel?["has_pins"] as? Bool
members = channel?["members"] as? [String]
if let members = channel?["members"] as? [String] {
self.members = members
if (Message(message: channel?["latest"] as? [String: Any])?.ts == nil) {
latest = Message(ts: channel?["latest"] as? String)
} else {
latest = Message(message: channel?["latest"] as? [String: Any])
}
}
internal init?(id:String?) {
@@ -0,0 +1,174 @@
//
// Client+EventDispatching.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal extension SlackClient {
func dispatch(event: [String: Any]) {
let event = Event(event: event)
if let type = event.type {
switch type {
case .Hello:
connected = true
slackEventsDelegate?.clientConnected()
case .Ok:
messageSent(event: event)
case .Message:
if (event.subtype != nil) {
messageDispatcher(event: event)
} else {
messageReceived(event: event)
}
case .UserTyping:
userTyping(event: event)
case .ChannelMarked, .IMMarked, .GroupMarked:
channelMarked(event: event)
case .ChannelCreated, .IMCreated:
channelCreated(event: event)
case .ChannelJoined, .GroupJoined:
channelJoined(event: event)
case .ChannelLeft, .GroupLeft:
channelLeft(event: event)
case .ChannelDeleted:
channelDeleted(event: event)
case .ChannelRenamed, .GroupRename:
channelRenamed(event: event)
case .ChannelArchive, .GroupArchive:
channelArchived(event: event, archived: true)
case .ChannelUnarchive, .GroupUnarchive:
channelArchived(event: event, archived: false)
case .ChannelHistoryChanged, .IMHistoryChanged, .GroupHistoryChanged:
channelHistoryChanged(event: event)
case .DNDUpdated:
doNotDisturbUpdated(event: event)
case .DNDUpatedUser:
doNotDisturbUserUpdated(event: event)
case .IMOpen, .GroupOpen:
open(event: event, open: true)
case .IMClose, .GroupClose:
open(event: event, open: false)
case .FileCreated:
processFile(event: event)
case .FileShared:
processFile(event: event)
case .FileUnshared:
processFile(event: event)
case .FilePublic:
processFile(event: event)
case .FilePrivate:
filePrivate(event: event)
case .FileChanged:
processFile(event: event)
case .FileDeleted:
deleteFile(event: event)
case .FileCommentAdded:
fileCommentAdded(event: event)
case .FileCommentEdited:
fileCommentEdited(event: event)
case .FileCommentDeleted:
fileCommentDeleted(event: event)
case .PinAdded:
pinAdded(event: event)
case .PinRemoved:
pinRemoved(event: event)
case .Pong:
pong(event: event)
case .PresenceChange:
presenceChange(event: event)
case .ManualPresenceChange:
manualPresenceChange(event: event)
case .PrefChange:
changePreference(event: event)
case .UserChange:
userChange(event: event)
case .TeamJoin:
teamJoin(event: event)
case .StarAdded:
itemStarred(event: event, star: true)
case .StarRemoved:
itemStarred(event: event, star: false)
case .ReactionAdded:
addedReaction(event: event)
case .ReactionRemoved:
removedReaction(event: event)
case .EmojiChanged:
emojiChanged(event: 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: event)
case .TeamPrefChange:
teamPreferenceChange(event: event)
case .TeamRename:
teamNameChange(event: event)
case .TeamDomainChange:
teamDomainChange(event: event)
case .EmailDomainChange:
emailDomainChange(event: event)
case .TeamProfileChange:
teamProfileChange(event: event)
case .TeamProfileDelete:
teamProfileDeleted(event: event)
case .TeamProfileReorder:
teamProfileReordered(event: event)
case .BotAdded:
bot(event: event)
case .BotChanged:
bot(event: 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: event)
case .SubteamSelfAdded:
subteamAddedSelf(event: event)
case.SubteamSelfRemoved:
subteamRemovedSelf(event: event)
case .Error:
print("Error: \(event)")
break
}
}
}
func messageDispatcher(event:Event) {
let subtype = MessageSubtype(rawValue: event.subtype!)!
switch subtype {
case .MessageChanged:
messageChanged(event: event)
case .MessageDeleted:
messageDeleted(event: event)
default:
messageReceived(event: event)
}
}
}
+547
View File
@@ -0,0 +1,547 @@
//
// Client+EventHandling.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Venice
internal extension SlackClient {
//MARK: - Pong
func pong(event: Event) {
pong = event.replyTo
}
//MARK: - Messages
func messageSent(event: Event) {
if let reply = event.replyTo, message = sentMessages["\(reply)"], channel = message.channel, ts = message.ts {
message.ts = event.ts
message.text = event.text
channels[channel]?.messages[ts] = message
messageEventsDelegate?.messageSent(message: 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: message)
}
}
func messageChanged(event: Event) {
if let id = event.channel?.id, nested = event.nestedMessage, ts = nested.ts {
channels[id]?.messages[ts] = nested
messageEventsDelegate?.messageChanged(message: nested)
}
}
func messageDeleted(event: Event) {
if let id = event.channel?.id, key = event.message?.deletedTs {
let message = channels[id]?.messages[key]
channels[id]?.messages.removeValue(forKey:key)
messageEventsDelegate?.messageDeleted(message: 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(channel: event.channel, user: event.user)
}
}
co { [weak self] in
let weakSelf = self
nap(for: 5.0.seconds)
if let index = weakSelf?.channels[channelID]?.usersTyping.index(of:userID) {
weakSelf?.channels[channelID]?.usersTyping.remove(at: index)
}
}
}
}
func channelMarked(event: Event) {
if let channel = event.channel, id = channel.id {
channels[id]?.lastRead = event.ts
channelEventsDelegate?.channelMarked(channel: channel, timestamp: event.ts)
}
//TODO: Recalculate unreads
}
func channelCreated(event: Event) {
if let channel = event.channel, id = channel.id {
channels[id] = channel
channelEventsDelegate?.channelCreated(channel: channel)
}
}
func channelDeleted(event: Event) {
if let channel = event.channel, id = channel.id {
channels.removeValue(forKey:id)
channelEventsDelegate?.channelDeleted(channel: channel)
}
}
func channelJoined(event: Event) {
if let channel = event.channel, id = channel.id {
channels[id] = event.channel
channelEventsDelegate?.channelJoined(channel: channel)
}
}
func channelLeft(event: Event) {
if let channel = event.channel, id = channel.id, userID = authenticatedUser?.id {
if let index = channels[id]?.members?.index(of:userID) {
channels[id]?.members?.remove(at: index)
channelEventsDelegate?.channelLeft(channel: channel)
}
}
}
func channelRenamed(event: Event) {
if let channel = event.channel, id = channel.id {
channels[id]?.name = channel.name
channelEventsDelegate?.channelRenamed(channel: channel)
}
}
func channelArchived(event: Event, archived: Bool) {
if let channel = event.channel, id = channel.id {
channels[id]?.isArchived = archived
channelEventsDelegate?.channelArchived(channel: 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: channel)
}
}
//MARK: - Do Not Disturb
func doNotDisturbUpdated(event: Event) {
if let dndStatus = event.dndStatus {
authenticatedUser?.doNotDisturbStatus = dndStatus
doNotDisturbEventsDelegate?.doNotDisturbUpdated(dndStatus: dndStatus)
}
}
func doNotDisturbUserUpdated(event: Event) {
if let dndStatus = event.dndStatus, user = event.user, id = user.id {
users[id]?.doNotDisturbStatus = dndStatus
doNotDisturbEventsDelegate?.doNotDisturbUserUpdated(dndStatus: 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(group: 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
}
}
files[id] = file
fileEventsDelegate?.fileProcessed(file: file)
}
}
func filePrivate(event: Event) {
if let file = event.file, id = file.id {
files[id]?.isPublic = false
fileEventsDelegate?.fileMadePrivate(file: file)
}
}
func deleteFile(event: Event) {
if let file = event.file, id = file.id {
if files[id] != nil {
files.removeValue(forKey:id)
}
fileEventsDelegate?.fileDeleted(file: 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: 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: file, comment: comment)
}
}
func fileCommentDeleted(event: Event) {
if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id {
files[id]?.comments.removeValue(forKey:commentID)
fileEventsDelegate?.fileCommentDeleted(file: 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: 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(item: event.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: item, star: star)
case "file":
starFile(item: item, star: star)
case "file_comment":
starComment(item: item)
default:
break
}
starEventsDelegate?.itemStarred(item: 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
}
}
}
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
}
}
}
}
}
func starComment(item: Item) {
if let file = item.file, id = file.id, comment = item.comment, commentID = comment.id {
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
}
reactionEventsDelegate?.reactionAdded(reaction: event.reaction, item: event.item, itemUser: event.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.removeValue(forKey:userID)
}
if (message.reactions[key]?.users.count == 0) {
message.reactions.removeValue(forKey:key)
}
}
}
case "file":
if let itemFile = item.file, id = itemFile.id, file = files[id] {
if file.reactions[key] != nil {
files[id]?.reactions[key]?.users.removeValue(forKey:userID)
}
if files[id]?.reactions[key]?.users.count == 0 {
files[id]?.reactions.removeValue(forKey: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.removeValue(forKey:userID)
}
if files[id]?.comments[commentID]?.reactions[key]?.users.count == 0 {
files[id]?.comments[commentID]?.reactions.removeValue(forKey:key)
}
}
break
default:
break
}
reactionEventsDelegate?.reactionRemoved(reaction: event.reaction, item: event.item, itemUser: event.itemUser)
}
}
//MARK: - Preferences
func changePreference(event: Event) {
if let name = event.name {
authenticatedUser?.preferences?[name] = event.value
slackEventsDelegate?.preferenceChanged(preference: 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: user)
}
}
//MARK: - User Presence
func presenceChange(event: Event) {
if let user = event.user, id = user.id {
users[id]?.presence = event.presence
slackEventsDelegate?.presenceChanged(user: user, presence: event.presence)
}
}
//MARK: - Team
func teamJoin(event: Event) {
if let user = event.user, id = user.id {
users[id] = user
teamEventsDelegate?.teamJoined(user: user)
}
}
func teamPlanChange(event: Event) {
if let plan = event.plan {
team?.plan = plan
teamEventsDelegate?.teamPlanChanged(plan: plan)
}
}
func teamPreferenceChange(event: Event) {
if let name = event.name {
team?.prefs?[name] = event.value
teamEventsDelegate?.teamPreferencesChanged(preference: name, value: event.value)
}
}
func teamNameChange(event: Event) {
if let name = event.name {
team?.name = name
teamEventsDelegate?.teamNameChanged(name: name)
}
}
func teamDomainChange(event: Event) {
if let domain = event.domain {
team?.domain = domain
teamEventsDelegate?.teamDomainChanged(domain: domain)
}
}
func emailDomainChange(event: Event) {
if let domain = event.emailDomain {
team?.emailDomain = domain
teamEventsDelegate?.teamEmailDomainChanged(domain: 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: bot)
}
}
//MARK: - Subteams
func subteam(event: Event) {
if let subteam = event.subteam, id = subteam.id {
userGroups[id] = subteam
subteamEventsDelegate?.subteamEvent(userGroup: subteam)
}
}
func subteamAddedSelf(event: Event) {
if let subteamID = event.subteamID, _ = authenticatedUser?.userGroups {
authenticatedUser?.userGroups![subteamID] = subteamID
subteamEventsDelegate?.subteamSelfAdded(subteamID: subteamID)
}
}
func subteamRemovedSelf(event: Event) {
if let subteamID = event.subteamID {
authenticatedUser?.userGroups?.removeValue(forKey:subteamID)
subteamEventsDelegate?.subteamSelfRemoved(subteamID: subteamID)
}
}
//MARK: - Team Profiles
func teamProfileChange(event: Event) {
for user in users {
if let fields = event.profile?.fields {
for key in fields.keys {
users[user.0]?.profile?.customProfile?.fields[key]?.updateProfileField(profile: fields[key])
}
}
}
teamProfileEventsDelegate?.teamProfileChanged(profile: event.profile)
}
func teamProfileDeleted(event: Event) {
for user in users {
if let id = event.profile?.fields.first?.0 {
users[user.0]?.profile?.customProfile?.fields[id] = nil
}
}
teamProfileEventsDelegate?.teamProfileDeleted(profile: event.profile)
}
func teamProfileReordered(event: Event) {
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
}
}
}
teamProfileEventsDelegate?.teamProfileReordered(profile: event.profile)
}
//MARK: - Authenticated User
func manualPresenceChange(event: Event) {
authenticatedUser?.presence = event.presence
slackEventsDelegate?.manualPresenceChanged(user: authenticatedUser, presence: event.presence)
}
}
@@ -1,5 +1,5 @@
//
// SlackKit.h
// Client+Utilities.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
@@ -21,10 +21,33 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
//! Project version number for SlackKit.
FOUNDATION_EXPORT double SlackKitVersionNumber;
//! Project version string for SlackKit.
FOUNDATION_EXPORT const unsigned char SlackKitVersionString[];
extension SlackClient {
//MARK: - User & Channel
public func getChannelIDByName(name: String) -> String? {
return channels.filter{$0.1.name == stripString(string: name)}.first?.0
}
public func getUserIDByName(name: String) -> String? {
return users.filter{$0.1.name == stripString(string: name)}.first?.0
}
public func getImIDForUserWithID(id: String, success: (imID: String?)->Void, failure: (error: SlackError)->Void) {
let ims = channels.filter{$0.1.isIM == true}
let channel = ims.filter{$0.1.user == id}.first
if let channel = channel {
success(imID: channel.0)
} else {
webAPI.openIM(userID: id, success: success, failure: failure)
}
}
//MARK: - Utilities
internal func stripString(string: String) -> String? {
var strippedString = string
if string[string.startIndex] == "@" || string[string.startIndex] == "#" {
strippedString.characters.remove(at: string.startIndex)
}
return strippedString
}
}
+180 -138
View File
@@ -21,10 +21,11 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import Starscream
import C7
import Jay
import WebSocket
public class Client: WebSocketDelegate {
public class SlackClient {
internal(set) public var connected = false
internal(set) public var authenticated = false
@@ -39,170 +40,189 @@ public class Client: WebSocketDelegate {
internal(set) public var sentMessages = [String: Message]()
//MARK: - Delegates
public var slackEventsDelegate: SlackEventsDelegate?
public var messageEventsDelegate: MessageEventsDelegate?
public var doNotDisturbEventsDelegate: DoNotDisturbEventsDelegate?
public var channelEventsDelegate: ChannelEventsDelegate?
public var groupEventsDelegate: GroupEventsDelegate?
public var fileEventsDelegate: FileEventsDelegate?
public var pinEventsDelegate: PinEventsDelegate?
public var starEventsDelegate: StarEventsDelegate?
public var reactionEventsDelegate: ReactionEventsDelegate?
public var teamEventsDelegate: TeamEventsDelegate?
public var subteamEventsDelegate: SubteamEventsDelegate?
private var token = "SLACK_AUTH_TOKEN"
public weak var slackEventsDelegate: SlackEventsDelegate?
public weak var messageEventsDelegate: MessageEventsDelegate?
public weak var doNotDisturbEventsDelegate: DoNotDisturbEventsDelegate?
public weak var channelEventsDelegate: ChannelEventsDelegate?
public weak var groupEventsDelegate: GroupEventsDelegate?
public weak var fileEventsDelegate: FileEventsDelegate?
public weak var pinEventsDelegate: PinEventsDelegate?
public weak var starEventsDelegate: StarEventsDelegate?
public weak var reactionEventsDelegate: ReactionEventsDelegate?
public weak var teamEventsDelegate: TeamEventsDelegate?
public weak var subteamEventsDelegate: SubteamEventsDelegate?
public weak var teamProfileEventsDelegate: TeamProfileEventsDelegate?
internal var token = "SLACK_AUTH_TOKEN"
public func setAuthToken(token: String) {
self.token = token
}
private var webSocket: WebSocket?
public var webAPI: SlackWebAPI {
return SlackWebAPI(slackClient: self)
}
internal var webSocket: WebSocket.Client?
internal var socket: Socket?
internal let api = NetworkInterface()
required public init() {
internal var ping: Double?
internal var pong: Double?
internal var pingInterval: Double?
internal var timeout: Double?
internal var reconnect: Bool?
required public init(apiToken: String) {
self.token = apiToken
}
public static let sharedInstance = Client()
//MARK: - Connection
public func connect() {
let request = NSURLRequest(URL: NSURL(string:"https://slack.com/api/rtm.start?token="+token)!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.currentQueue()!) {
(response, data, error) -> Void in
guard let data = data else {
return
}
do {
let result = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject]
if (result["ok"] as! Bool == true) {
self.initialSetup(result)
let socketURL = NSURL(string: result["url"] as! String)
self.webSocket = WebSocket(url: socketURL!)
self.webSocket?.delegate = self
self.webSocket?.connect()
public func connect(simpleLatest: Bool? = nil, noUnreads: Bool? = nil, mpimAware: Bool? = nil, pingInterval: Double? = nil, timeout: Double? = nil, reconnect: Bool? = nil) {
self.pingInterval = pingInterval
self.timeout = timeout
self.reconnect = reconnect
webAPI.rtmStart(simpleLatest: simpleLatest, noUnreads: noUnreads, mpimAware: mpimAware, success: {
(response) -> Void in
self.initialSetup(json: response)
if let socketURL = response["url"] as? String {
do {
let uri = try URI(socketURL)
self.webSocket = try WebSocket.Client(uri: uri, onConnect: {(socket) in
self.setupSocket(socket: socket)
if let pingInterval = self.pingInterval {
self.pingRTMServerAtInterval(interval: pingInterval)
}
})
try self.webSocket?.connect(uri.description)
} catch _ {
}
} catch _ {
print(error)
}
}
}, failure:nil)
}
//MARK: - Message send
/*TO-DO: Bug in Zewo/WebSocket
public func disconnect() {
_ = try? socket?.close()
}*/
//MARK: - RTM Message send
public func sendMessage(message: String, channelID: String) {
if (connected) {
if let data = formatMessageToSlackJsonString(msg: message, channel: channelID) {
let string = NSString(data: data, encoding: NSUTF8StringEncoding)
self.webSocket?.writeString(string as! String)
if let data = formatMessageToSlackJsonString(message: message, channel: channelID) {
if let string = try? data.string() {
_ = try? socket?.send(string)
}
}
}
}
private func formatMessageToSlackJsonString(message: (msg: String, channel: String)) -> NSData? {
let json: [String: AnyObject] = [
"id": NSDate().timeIntervalSince1970,
private func formatMessageToSlackJsonString(message: String, channel: String) -> Data? {
let json: [String: Any] = [
"id": Time.slackTimestamp(),
"type": "message",
"channel": message.channel,
"text": slackFormatEscaping(message.msg)
"channel": channel,
"text": message.slackFormatEscaping()
]
addSentMessage(json)
do {
let data = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions.PrettyPrinted)
return data
}
catch _ {
let bytes = try Jay().dataFromJson(json)
return Data(bytes)
} catch {
return nil
}
}
private func addSentMessage(dictionary: [String: AnyObject]) {
private func addSentMessage(dictionary: [String: Any]) {
var message = dictionary
let ts = message["id"] as? NSNumber
message.removeValueForKey("id")
message["ts"] = ts?.stringValue
let ts = message["id"] as? Int
message.removeValue(forKey:"id")
message["ts"] = "\(ts)"
message["user"] = self.authenticatedUser?.id
sentMessages[ts!.stringValue] = Message(message: message)
sentMessages["\(ts)"] = Message(message: message)
}
private func slackFormatEscaping(string: String) -> String {
var escapedString = string.stringByReplacingOccurrencesOfString("&", withString: "&amp;")
escapedString = escapedString.stringByReplacingOccurrencesOfString("<", withString: "&lt;")
escapedString = escapedString.stringByReplacingOccurrencesOfString(">", withString: "&gt;")
return escapedString
//MARK: - RTM Ping
private func pingRTMServerAtInterval(interval: Double) {
co { [weak self] in
let weakSelf = self
repeat {
nap(for: interval)
weakSelf?.sendRTMPing()
} while weakSelf?.connected == true && weakSelf?.timeoutCheck() == true
//weakSelf?.disconnect()
}
}
private func sendRTMPing() {
if connected {
let json: [String: Any] = [
"id": Double.slackTimestamp(),
"type": "ping",
]
do {
let data = try Jay().dataFromJson(json)
let string = try data.string()
ping = json["id"] as? Double
try socket?.send(string)
}
catch _ {
}
}
}
private func timeoutCheck() -> Bool {
if let pong = pong, ping = ping, timeout = timeout {
if pong - ping < timeout {
return true
} else {
return false
}
// Ping-pong or timeout not configured
} else {
return true
}
}
//MARK: - Client setup
private func initialSetup(json: [String: AnyObject]) {
team = Team(team: json["team"] as? [String: AnyObject])
authenticatedUser = User(user: json["self"] as? [String: AnyObject])
authenticatedUser?.doNotDisturbStatus = DoNotDisturbStatus(status: json["dnd"] as? [String: AnyObject])
enumerateUsers(json["users"] as? Array)
enumerateChannels(json["channels"] as? Array)
enumerateGroups(json["groups"] as? Array)
enumerateMPIMs(json["mpims"] as? Array)
enumerateIMs(json["ims"] as? Array)
enumerateBots(json["bots"] as? Array)
enumerateSubteams(json["subteams"] as? [String: AnyObject])
private func initialSetup(json: [String: Any]) {
team = Team(team: json["team"] as? [String: Any])
authenticatedUser = User(user: json["self"] as? [String: Any])
authenticatedUser?.doNotDisturbStatus = DoNotDisturbStatus(status: json["dnd"] as? [String: Any])
enumerateObjects(array: json["users"] as? Array) { (user) in self.addUser(aUser: user) }
enumerateObjects(array: json["channels"] as? Array) { (channel) in self.addChannel(aChannel: channel) }
enumerateObjects(array: json["groups"] as? Array) { (group) in self.addChannel(aChannel: group) }
enumerateObjects(array: json["mpims"] as? Array) { (mpim) in self.addChannel(aChannel: mpim) }
enumerateObjects(array: json["ims"] as? Array) { (ims) in self.addChannel(aChannel: ims) }
enumerateObjects(array: json["bots"] as? Array) { (bots) in self.addBot(aBot: bots) }
enumerateSubteams(subteams: json["subteams"] as? [String: Any])
}
private func enumerateUsers(users: [AnyObject]?) {
if let users = users {
for user in users {
let u = User(user: user as? [String: AnyObject])
self.users[u!.id!] = u
}
private func addUser(aUser: [String: Any]) {
if let user = User(user: aUser), id = user.id {
users[id] = user
}
}
private func enumerateChannels(channels: [AnyObject]?) {
if let channels = channels {
for channel in channels {
let c = Channel(channel: channel as? [String: AnyObject])
self.channels[c!.id!] = c
}
private func addChannel(aChannel: [String: Any]) {
if let channel = Channel(channel: aChannel), id = channel.id {
channels[id] = channel
}
}
private func enumerateGroups(groups: [AnyObject]?) {
if let groups = groups {
for group in groups {
let g = Channel(channel: group as? [String: AnyObject])
self.channels[g!.id!] = g
}
private func addBot(aBot: [String: Any]) {
if let bot = Bot(bot: aBot), id = bot.id {
bots[id] = bot
}
}
private func enumerateIMs(ims: [AnyObject]?) {
if let ims = ims {
for im in ims {
let i = Channel(channel: im as? [String: AnyObject])
self.channels[i!.id!] = i
}
}
}
private func enumerateMPIMs(mpims: [AnyObject]?) {
if let mpims = mpims {
for mpim in mpims {
let m = Channel(channel: mpim as? [String: AnyObject])
self.channels[m!.id!] = m
}
}
}
private func enumerateBots(bots: [AnyObject]?) {
if let bots = bots {
for bot in bots {
let b = Bot(bot: bot as? [String: AnyObject])
self.bots[b!.id!] = b
}
}
}
private func enumerateSubteams(subteams: [String: AnyObject]?) {
private func enumerateSubteams(subteams: [String: Any]?) {
if let subteams = subteams {
if let all = subteams["all"] as? [[String: AnyObject]] {
if let all = subteams["all"] as? [Any] {
for item in all {
let u = UserGroup(userGroup: item)
let u = UserGroup(userGroup: item as? [String: Any])
self.userGroups[u!.id!] = u
}
}
@@ -215,32 +235,54 @@ public class Client: WebSocketDelegate {
}
}
// MARK: - WebSocketDelegate
public func websocketDidConnect(socket: WebSocket) {
}
public func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
connected = false
authenticated = false
webSocket = nil
if let delegate = slackEventsDelegate {
delegate.clientDisconnected()
// MARK: - Utilities
private func enumerateObjects(array: [Any]?, initalizer: ([String: Any])-> Void) {
if let array = array {
for object in array {
if let dictionary = object as? [String: Any] {
initalizer(dictionary)
}
}
}
}
public func websocketDidReceiveMessage(socket: WebSocket, text: String) {
guard let data = text.dataUsingEncoding(NSUTF8StringEncoding) else {
return
// MARK: - WebSocket
private func setupSocket(socket: Socket) {
socket.onText {(message) in
self.websocketDidReceive(message: message)
}
socket.onPing { (data) in try socket.pong() }
socket.onPong { (data) in try socket.ping() }
socket.onClose{ (code: CloseCode?, reason: String?) in
self.websocketDidDisconnect(closeCode: code, error: reason)
}
self.socket = socket
}
private func websocketDidReceive(message: String) {
do {
try EventDispatcher.eventDispatcher(NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! [String: AnyObject])
let json = try Jay().jsonFromData(message.data.bytes)
if let event = json as? [String: Any] {
dispatch(event:event)
}
}
catch _ {
}
}
public func websocketDidReceiveData(socket: WebSocket, data: NSData) {
private func websocketDidDisconnect(closeCode: CloseCode?, error: String?) {
connected = false
authenticated = false
webSocket = nil
socket = nil
authenticatedUser = nil
slackEventsDelegate?.clientDisconnected()
if reconnect == true {
connect(pingInterval: pingInterval, timeout: timeout, reconnect: reconnect)
}
}
}
+25 -18
View File
@@ -62,6 +62,7 @@ internal enum EventType: String {
case FileCommentDeleted = "file_comment_deleted"
case PinAdded = "pin_added"
case PinRemoved = "pin_removed"
case Pong = "pong"
case PresenceChange = "presence_change"
case ManualPresenceChange = "manual_presence_change"
case PrefChange = "pref_change"
@@ -78,10 +79,14 @@ internal enum EventType: String {
case TeamRename = "team_rename"
case TeamDomainChange = "team_domain_change"
case EmailDomainChange = "email_domain_change"
case TeamProfileChange = "team_profile_change"
case TeamProfileDelete = "team_profile_delete"
case TeamProfileReorder = "team_profile_reorder"
case BotAdded = "bot_added"
case BotChanged = "bot_changed"
case AccountsChanged = "accounts_changed"
case TeamMigrationStarted = "team_migration_started"
case ReconnectURL = "reconnect_url"
case SubteamCreated = "subteam_created"
case SubteamUpdated = "subteam_updated"
case SubteamSelfAdded = "subteam_self_added"
@@ -132,14 +137,13 @@ internal struct Event {
let fileID: String?
let presence: String?
let name: String?
let value: AnyObject?
let value: Any?
let plan: String?
let url: String?
let domain: String?
let emailDomain: String?
let reaction: String?
let replyTo: Double?
let reactions: [[String: AnyObject]]?
let edited: Edited?
let bot: Bot?
let channel: Channel?
@@ -148,12 +152,14 @@ internal struct Event {
let file: File?
let message: Message?
let nestedMessage: Message?
let itemUser: String?
let item: Item?
let dndStatus: DoNotDisturbStatus?
let subteam: UserGroup?
let subteamID: String?
var profile: CustomProfile?
init(event:[String: AnyObject]) {
init(event:[String: Any]) {
if let eventType = event["type"] as? String {
type = EventType(rawValue:eventType)
} else {
@@ -179,39 +185,40 @@ internal struct Event {
emailDomain = event["email_domain"] as? String
reaction = event["reaction"] as? String
replyTo = event["reply_to"] as? Double
reactions = event["reactions"] as? [[String: AnyObject]]
bot = Bot(bot: event["bot"] as? [String: AnyObject])
edited = Edited(edited:event["edited"] as? [String: AnyObject])
dndStatus = DoNotDisturbStatus(status: event["dnd_status"] as? [String: AnyObject])
item = Item(item: event["item"] as? [String: AnyObject])
subteam = UserGroup(userGroup: event["subteam"] as? [String: AnyObject])
bot = Bot(bot: event["bot"] as? [String: Any])
edited = Edited(edited:event["edited"] as? [String: Any])
dndStatus = DoNotDisturbStatus(status: event["dnd_status"] as? [String: Any])
itemUser = event["item_user"] as? String
item = Item(item: event["item"] as? [String: Any])
subteam = UserGroup(userGroup: event["subteam"] as? [String: Any])
subteamID = event["subteam_id"] as? String
message = Message(message: event)
nestedMessage = Message(message: event["message"] as? [String: AnyObject])
nestedMessage = Message(message: event["message"] as? [String: Any])
profile = CustomProfile(profile: event["profile"] as? [String: Any])
// Comment, Channel, User, and File can come across as Strings or Dictionaries
if (Comment(comment: event["comment"] as? [String: AnyObject])?.id == nil) {
if (Comment(comment: event["comment"] as? [String: Any])?.id == nil) {
comment = Comment(id: event["comment"] as? String)
} else {
comment = Comment(comment: event["comment"] as? [String: AnyObject])
comment = Comment(comment: event["comment"] as? [String: Any])
}
if (User(user: event["user"] as? [String: AnyObject])?.id == nil) {
if (User(user: event["user"] as? [String: Any])?.id == nil) {
user = User(id: event["user"] as? String)
} else {
user = User(user: event["user"] as? [String: AnyObject])
user = User(user: event["user"] as? [String: Any])
}
if (File(file: event["file"] as? [String: AnyObject])?.id == nil) {
if (File(file: event["file"] as? [String: Any])?.id == nil) {
file = File(id: event["file"] as? String)
} else {
file = File(file: event["file"] as? [String: AnyObject])
file = File(file: event["file"] as? [String: Any])
}
if (Channel(channel: event["channel"] as? [String: AnyObject])?.id == nil) {
if (Channel(channel: event["channel"] as? [String: Any])?.id == nil) {
channel = Channel(id: event["channel"] as? String)
} else {
channel = Channel(channel: event["channel"] as? [String: AnyObject])
channel = Channel(channel: event["channel"] as? [String: Any])
}
}
+21 -17
View File
@@ -21,26 +21,24 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
public protocol SlackEventsDelegate {
public protocol SlackEventsDelegate: class {
func clientConnected()
func clientDisconnected()
func preferenceChanged(preference: String, value: AnyObject)
func preferenceChanged(preference: String, value: Any)
func userChanged(user: User)
func presenceChanged(user: User?, presence: String?)
func manualPresenceChanged(user: User?, presence: String?)
func botEvent(bot: Bot)
}
public protocol MessageEventsDelegate {
public protocol MessageEventsDelegate: class {
func messageSent(message: Message)
func messageReceived(message: Message)
func messageChanged(message: Message)
func messageDeleted(message: Message?)
}
public protocol ChannelEventsDelegate {
public protocol ChannelEventsDelegate: class {
func userTyping(channel: Channel?, user: User?)
func channelMarked(channel: Channel, timestamp: String?)
func channelCreated(channel: Channel)
@@ -52,16 +50,16 @@ public protocol ChannelEventsDelegate {
func channelLeft(channel: Channel)
}
public protocol DoNotDisturbEventsDelegate {
public protocol DoNotDisturbEventsDelegate: class {
func doNotDisturbUpdated(dndStatus: DoNotDisturbStatus)
func doNotDisturbUserUpdated(dndStatus: DoNotDisturbStatus, user: User?)
}
public protocol GroupEventsDelegate {
public protocol GroupEventsDelegate: class {
func groupOpened(group: Channel)
}
public protocol FileEventsDelegate {
public protocol FileEventsDelegate: class {
func fileProcessed(file: File)
func fileMadePrivate(file: File)
func fileDeleted(file: File)
@@ -70,32 +68,38 @@ public protocol FileEventsDelegate {
func fileCommentDeleted(file: File, comment: Comment)
}
public protocol PinEventsDelegate {
public protocol PinEventsDelegate: class {
func itemPinned(item: Item?, channel: Channel?)
func itemUnpinned(item: Item?, channel: Channel?)
}
public protocol StarEventsDelegate {
public protocol StarEventsDelegate: class {
func itemStarred(item: Item, star: Bool)
}
public protocol ReactionEventsDelegate {
func reactionAdded(reaction: String?, item: Item?)
func reactionRemoved(reaction: String?, item: Item?)
public protocol ReactionEventsDelegate: class {
func reactionAdded(reaction: String?, item: Item?, itemUser: String?)
func reactionRemoved(reaction: String?, item: Item?, itemUser: String?)
}
public protocol TeamEventsDelegate {
public protocol TeamEventsDelegate: class {
func teamJoined(user: User)
func teamPlanChanged(plan: String)
func teamPreferencesChanged(preference: String, value: AnyObject)
func teamPreferencesChanged(preference: String, value: Any)
func teamNameChanged(name: String)
func teamDomainChanged(domain: String)
func teamEmailDomainChanged(domain: String)
func teamEmojiChanged()
}
public protocol SubteamEventsDelegate {
public protocol SubteamEventsDelegate: class {
func subteamEvent(userGroup: UserGroup)
func subteamSelfAdded(subteamID: String)
func subteamSelfRemoved(subteamID: String)
}
public protocol TeamProfileEventsDelegate: class {
func teamProfileChanged(profile: CustomProfile?)
func teamProfileDeleted(profile: CustomProfile?)
func teamProfileReordered(profile: CustomProfile?)
}
-159
View File
@@ -1,159 +0,0 @@
//
// EventDispatcher.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal struct EventDispatcher {
static func eventDispatcher(event: [String: AnyObject]) {
let event = Event(event: event)
if let type = event.type {
switch type {
case .Hello:
EventHandler.connected()
case .Ok:
EventHandler.messageSent(event)
case .Message:
if (event.subtype != nil) {
messageDispatcher(event)
} else {
EventHandler.messageReceived(event)
}
case .UserTyping:
EventHandler.userTyping(event)
case .ChannelMarked, .IMMarked, .GroupMarked:
EventHandler.channelMarked(event)
case .ChannelCreated, .IMCreated:
EventHandler.channelCreated(event)
case .ChannelJoined, .GroupJoined:
EventHandler.channelJoined(event)
case .ChannelLeft, .GroupLeft:
EventHandler.channelLeft(event)
case .ChannelDeleted:
EventHandler.channelDeleted(event)
case .ChannelRenamed, .GroupRename:
EventHandler.channelRenamed(event)
case .ChannelArchive, .GroupArchive:
EventHandler.channelArchived(event, archived: true)
case .ChannelUnarchive, .GroupUnarchive:
EventHandler.channelArchived(event, archived: false)
case .ChannelHistoryChanged, .IMHistoryChanged, .GroupHistoryChanged:
EventHandler.channelHistoryChanged(event)
case .DNDUpdated:
EventHandler.doNotDisturbUpdated(event)
case .DNDUpatedUser:
EventHandler.doNotDisturbUserUpdated(event)
case .IMOpen, .GroupOpen:
EventHandler.open(event, open: true)
case .IMClose, .GroupClose:
EventHandler.open(event, open: false)
case .FileCreated:
EventHandler.processFile(event)
case .FileShared:
EventHandler.processFile(event)
case .FileUnshared:
EventHandler.processFile(event)
case .FilePublic:
EventHandler.processFile(event)
case .FilePrivate:
EventHandler.filePrivate(event)
case .FileChanged:
EventHandler.processFile(event)
case .FileDeleted:
EventHandler.deleteFile(event)
case .FileCommentAdded:
EventHandler.fileCommentAdded(event)
case .FileCommentEdited:
EventHandler.fileCommentEdited(event)
case .FileCommentDeleted:
EventHandler.fileCommentDeleted(event)
case .PinAdded:
EventHandler.pinAdded(event)
case .PinRemoved:
EventHandler.pinRemoved(event)
case .PresenceChange:
EventHandler.presenceChange(event)
case .ManualPresenceChange:
EventHandler.manualPresenceChange(event)
case .PrefChange:
EventHandler.changePreference(event)
case .UserChange:
EventHandler.userChange(event)
case .TeamJoin:
EventHandler.teamJoin(event)
case .StarAdded:
EventHandler.itemStarred(event, star: true)
case .StarRemoved:
EventHandler.itemStarred(event, star: false)
case .ReactionAdded:
EventHandler.addedReaction(event)
case .ReactionRemoved:
EventHandler.removedReaction(event)
case .EmojiChanged:
EventHandler.emojiChanged(event)
case .CommandsChanged:
// Not implemented per Slack documentation.
break
case .TeamPlanChange:
EventHandler.teamPlanChange(event)
case .TeamPrefChange:
EventHandler.teamPreferenceChange(event)
case .TeamRename:
EventHandler.teamNameChange(event)
case .TeamDomainChange:
EventHandler.teamDomainChange(event)
case .EmailDomainChange:
EventHandler.emailDomainChange(event)
case .BotAdded:
EventHandler.bot(event)
case .BotChanged:
EventHandler.bot(event)
case .AccountsChanged:
// Not implemented per Slack documentation.
break
case .TeamMigrationStarted:
Client.sharedInstance.connect()
case .SubteamCreated, .SubteamUpdated:
EventHandler.subteam(event)
case .SubteamSelfAdded:
EventHandler.subteamAddedSelf(event)
case.SubteamSelfRemoved:
EventHandler.subteamRemovedSelf(event)
case .Error:
print("Error: \(event)")
break
}
}
}
static func messageDispatcher(event:Event) {
let subtype = MessageSubtype(rawValue: event.subtype!)!
switch subtype {
case .MessageChanged:
EventHandler.messageChanged(event)
case .MessageDeleted:
EventHandler.messageDeleted(event)
default:
EventHandler.messageReceived(event)
}
}
}
-600
View File
@@ -1,600 +0,0 @@
//
// EventHandler.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
internal struct EventHandler {
//MARK: - Initial connection
static func connected() {
Client.sharedInstance.connected = true
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.clientConnected()
}
}
//MARK: - Messages
static func messageSent(event: Event) {
if let reply = event.replyTo, message = Client.sharedInstance.sentMessages[NSNumber(double: reply).stringValue], channel = message.channel, ts = message.ts {
message.ts = event.ts
message.text = event.text
Client.sharedInstance.channels[channel]?.messages[ts] = message
if let delegate = Client.sharedInstance.messageEventsDelegate {
delegate.messageSent(message)
}
}
}
static func messageReceived(event: Event) {
if let channel = event.channel, message = event.message, id = channel.id, ts = message.ts {
Client.sharedInstance.channels[id]?.messages[ts] = message
if let delegate = Client.sharedInstance.messageEventsDelegate {
delegate.messageReceived(message)
}
}
}
static func messageChanged(event: Event) {
if let id = event.channel?.id, nested = event.nestedMessage, ts = nested.ts {
Client.sharedInstance.channels[id]?.messages[ts] = nested
if let delegate = Client.sharedInstance.messageEventsDelegate {
delegate.messageChanged(nested)
}
}
}
static func messageDeleted(event: Event) {
if let id = event.channel?.id, key = event.message?.deletedTs {
let message = Client.sharedInstance.channels[id]?.messages[key]
Client.sharedInstance.channels[id]?.messages.removeValueForKey(key)
if let delegate = Client.sharedInstance.messageEventsDelegate {
delegate.messageDeleted(message)
}
}
}
//MARK: - Channels
static func userTyping(event: Event) {
if let channelID = event.channel?.id, userID = event.user?.id {
if let _ = Client.sharedInstance.channels[channelID] {
if (!Client.sharedInstance.channels[channelID]!.usersTyping.contains(userID)) {
Client.sharedInstance.channels[channelID]?.usersTyping.append(userID)
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.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 = Client.sharedInstance.channels[channelID]?.usersTyping.indexOf(userID) {
Client.sharedInstance.channels[channelID]?.usersTyping.removeAtIndex(index)
}
}
}
}
static func channelMarked(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id]?.lastRead = event.ts
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelMarked(channel, timestamp: event.ts)
}
}
//TODO: Recalculate unreads
}
static func channelCreated(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id] = channel
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelCreated(channel)
}
}
}
static func channelDeleted(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels.removeValueForKey(id)
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelDeleted(channel)
}
}
}
static func channelJoined(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id] = event.channel
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelJoined(channel)
}
}
}
static func channelLeft(event: Event) {
if let channel = event.channel, id = channel.id, userID = Client.sharedInstance.authenticatedUser?.id {
if let index = Client.sharedInstance.channels[id]?.members.indexOf(userID) {
Client.sharedInstance.channels[id]?.members.removeAtIndex(index)
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelLeft(channel)
}
}
}
}
static func channelRenamed(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id]?.name = channel.name
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelRenamed(channel)
}
}
}
static func channelArchived(event: Event, archived: Bool) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id]?.isArchived = archived
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelArchived(channel)
}
}
}
static func channelHistoryChanged(event: Event) {
if let channel = event.channel {
//TODO: Reload chat history if there are any cached messages before latest
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelHistoryChanged(channel)
}
}
}
//MARK: - Do Not Disturb
static func doNotDisturbUpdated(event: Event) {
if let dndStatus = event.dndStatus {
Client.sharedInstance.authenticatedUser?.doNotDisturbStatus = dndStatus
if let delegate = Client.sharedInstance.doNotDisturbEventsDelegate {
delegate.doNotDisturbUpdated(dndStatus)
}
}
}
static func doNotDisturbUserUpdated(event: Event) {
if let dndStatus = event.dndStatus, user = event.user, id = user.id {
Client.sharedInstance.users[id]?.doNotDisturbStatus = dndStatus
if let delegate = Client.sharedInstance.doNotDisturbEventsDelegate {
delegate.doNotDisturbUserUpdated(dndStatus, user: user)
}
}
}
//MARK: - IM & Group Open/Close
static func open(event: Event, open: Bool) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id]?.isOpen = open
if let delegate = Client.sharedInstance.groupEventsDelegate {
delegate.groupOpened(channel)
}
}
}
//MARK: - Files
static func processFile(event: Event) {
if let file = event.file, id = file.id {
if let comment = file.initialComment, commentID = comment.id {
if Client.sharedInstance.files[id]?.comments[commentID] == nil {
Client.sharedInstance.files[id]?.comments[commentID] = comment
}
}
Client.sharedInstance.files[id] = file
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileProcessed(file)
}
}
}
static func filePrivate(event: Event) {
if let file = event.file, id = file.id {
Client.sharedInstance.files[id]?.isPublic = false
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileMadePrivate(file)
}
}
}
static func deleteFile(event: Event) {
if let file = event.file, id = file.id {
if Client.sharedInstance.files[id] != nil {
Client.sharedInstance.files.removeValueForKey(id)
}
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileDeleted(file)
}
}
}
static func fileCommentAdded(event: Event) {
if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id {
Client.sharedInstance.files[id]?.comments[commentID] = comment
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileCommentAdded(file, comment: comment)
}
}
}
static func fileCommentEdited(event: Event) {
if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id {
Client.sharedInstance.files[id]?.comments[commentID]?.comment = comment.comment
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileCommentEdited(file, comment: comment)
}
}
}
static func fileCommentDeleted(event: Event) {
if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id {
Client.sharedInstance.files[id]?.comments.removeValueForKey(commentID)
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileCommentDeleted(file, comment: comment)
}
}
}
//MARK: - Pins
static func pinAdded(event: Event) {
if let id = event.channelID, item = event.item {
Client.sharedInstance.channels[id]?.pinnedItems.append(item)
if let delegate = Client.sharedInstance.pinEventsDelegate {
delegate.itemPinned(item, channel: Client.sharedInstance.channels[id])
}
}
}
static func pinRemoved(event: Event) {
if let id = event.channelID {
if let pins = Client.sharedInstance.channels[id]?.pinnedItems.filter({$0 != event.item}) {
Client.sharedInstance.channels[id]?.pinnedItems = pins
}
if let delegate = Client.sharedInstance.pinEventsDelegate {
delegate.itemUnpinned(event.item, channel: Client.sharedInstance.channels[id])
}
}
}
//MARK: - Stars
static 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
}
if let delegate = Client.sharedInstance.starEventsDelegate {
delegate.itemStarred(item, star: star)
}
}
}
static func starMessage(item: Item, star: Bool) {
if let message = item.message, ts = message.ts, channel = item.channel {
if let _ = Client.sharedInstance.channels[channel]?.messages[ts] {
Client.sharedInstance.channels[channel]?.messages[ts]?.isStarred = star
}
}
}
static func starFile(item: Item, star: Bool) {
if let file = item.file, id = file.id {
Client.sharedInstance.files[id]?.isStarred = star
if let stars = Client.sharedInstance.files[id]?.stars {
if star == true {
Client.sharedInstance.files[id]?.stars = stars + 1
} else {
if stars > 0 {
Client.sharedInstance.files[id]?.stars = stars - 1
}
}
}
}
}
static func starComment(item: Item) {
if let file = item.file, id = file.id, comment = item.comment, commentID = comment.id {
Client.sharedInstance.files[id]?.comments[commentID] = comment
}
}
//MARK: - Reactions
static func addedReaction(event: Event) {
if let item = event.item, type = item.type, key = event.reaction, userID = event.user?.id {
switch type {
case "message":
if let channel = item.channel, ts = item.ts {
if let message = Client.sharedInstance.channels[channel]?.messages[ts] {
if (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 = Client.sharedInstance.files[id] {
if file.reactions[key] == nil {
Client.sharedInstance.files[id]?.reactions[key] = Reaction(name: event.reaction, user: userID)
} else {
Client.sharedInstance.files[id]?.reactions[key]?.users[userID] = userID
}
}
case "file_comment":
if let id = item.file?.id, file = Client.sharedInstance.files[id], commentID = item.fileCommentID {
if file.comments[commentID]?.reactions[key] == nil {
Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key] = Reaction(name: event.reaction, user: userID)
} else {
Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key]?.users[userID] = userID
}
}
break
default:
break
}
if let delegate = Client.sharedInstance.reactionEventsDelegate {
delegate.reactionAdded(event.reaction, item: event.item)
}
}
}
static func removedReaction(event: Event) {
if let item = event.item, type = item.type, key = event.reaction, userID = event.user?.id {
switch type {
case "message":
if let channel = item.channel, ts = item.ts {
if let message = Client.sharedInstance.channels[channel]?.messages[ts] {
if (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 = Client.sharedInstance.files[id] {
if file.reactions[key] != nil {
Client.sharedInstance.files[id]?.reactions[key]?.users.removeValueForKey(userID)
}
if Client.sharedInstance.files[id]?.reactions[key]?.users.count == 0 {
Client.sharedInstance.files[id]?.reactions.removeValueForKey(key)
}
}
case "file_comment":
if let id = item.file?.id, file = Client.sharedInstance.files[id], commentID = item.fileCommentID {
if file.comments[commentID]?.reactions[key] != nil {
Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key]?.users.removeValueForKey(userID)
}
if Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key]?.users.count == 0 {
Client.sharedInstance.files[id]?.comments[commentID]?.reactions.removeValueForKey(key)
}
}
break
default:
break
}
if let delegate = Client.sharedInstance.reactionEventsDelegate {
delegate.reactionAdded(event.reaction, item: event.item)
}
}
}
//MARK: - Preferences
static func changePreference(event: Event) {
if let name = event.name {
Client.sharedInstance.authenticatedUser?.preferences?[name] = event.value
if let delegate = Client.sharedInstance.slackEventsDelegate, value = event.value {
delegate.preferenceChanged(name, value: value)
}
}
}
//Mark: - User Change
static func userChange(event: Event) {
if let user = event.user, id = user.id {
let preferences = Client.sharedInstance.users[id]?.preferences
Client.sharedInstance.users[id] = user
Client.sharedInstance.users[id]?.preferences = preferences
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.userChanged(user)
}
}
}
//MARK: - User Presence
static func presenceChange(event: Event) {
if let user = event.user, id = user.id {
Client.sharedInstance.users[id]?.presence = event.presence
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.presenceChanged(user, presence: event.presence)
}
}
}
//MARK: - Team
static func teamJoin(event: Event) {
if let user = event.user, id = user.id {
Client.sharedInstance.users[id] = user
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamJoined(user)
}
}
}
static func teamPlanChange(event: Event) {
if let plan = event.plan {
Client.sharedInstance.team?.plan = plan
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamPlanChanged(plan)
}
}
}
static func teamPreferenceChange(event: Event) {
if let name = event.name {
Client.sharedInstance.team?.prefs?[name] = event.value
if let delegate = Client.sharedInstance.teamEventsDelegate, value = event.value {
delegate.teamPreferencesChanged(name, value: value)
}
}
}
static func teamNameChange(event: Event) {
if let name = event.name {
Client.sharedInstance.team?.name = name
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamNameChanged(name)
}
}
}
static func teamDomainChange(event: Event) {
if let domain = event.domain {
Client.sharedInstance.team?.domain = domain
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamDomainChanged(domain)
}
}
}
static func emailDomainChange(event: Event) {
if let domain = event.emailDomain {
Client.sharedInstance.team?.emailDomain = domain
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamEmailDomainChanged(domain)
}
}
}
static func emojiChanged(event: Event) {
//TODO: Call emoji.list here
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamEmojiChanged()
}
}
//MARK: - Bots
static func bot(event: Event) {
if let bot = event.bot, id = bot.id {
Client.sharedInstance.bots[id] = bot
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.botEvent(bot)
}
}
}
//MARK: - Subteams
static func subteam(event: Event) {
if let subteam = event.subteam, id = subteam.id {
Client.sharedInstance.userGroups[id] = subteam
if let delegate = Client.sharedInstance.subteamEventsDelegate {
delegate.subteamEvent(subteam)
}
}
}
static func subteamAddedSelf(event: Event) {
if let subteamID = event.subteamID, _ = Client.sharedInstance.authenticatedUser?.userGroups {
Client.sharedInstance.authenticatedUser?.userGroups![subteamID] = subteamID
if let delegate = Client.sharedInstance.subteamEventsDelegate {
delegate.subteamSelfAdded(subteamID)
}
}
}
static func subteamRemovedSelf(event: Event) {
if let subteamID = event.subteamID {
Client.sharedInstance.authenticatedUser?.userGroups?.removeValueForKey(subteamID)
if let delegate = Client.sharedInstance.subteamEventsDelegate {
delegate.subteamSelfRemoved(subteamID)
}
}
}
//MARK: - Authenticated User
static func manualPresenceChange(event: Event) {
Client.sharedInstance.authenticatedUser?.presence = event.presence
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.manualPresenceChanged(Client.sharedInstance.authenticatedUser, presence: event.presence)
}
}
}
+99
View File
@@ -0,0 +1,99 @@
//
// Extensions.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import C7
#if os(Linux)
import Glibc
#else
import Darwin.C
#endif
public typealias Time=Double
public extension Double {
static func slackTimestamp() -> Double {
#if os(Linux)
return Double(time(nil))
#else
var clock: clock_serv_t = clock_serv_t()
var timeSpecBuffer: mach_timespec_t = mach_timespec_t(tv_sec: 0, tv_nsec: 0)
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &clock)
clock_get_time(clock, &timeSpecBuffer)
mach_port_deallocate(mach_task_self_, clock)
return Double(timeSpecBuffer.tv_sec) + Double(timeSpecBuffer.tv_nsec) * 0.000000001
#endif
}
}
internal extension String {
func slackFormatEscaping() -> String {
var escapedString = self
escapedString.replace(string: "&", with: "&amp;")
escapedString.replace(string: "<", with: "&lt;")
escapedString.replace(string: ">", with: "&gt;")
return escapedString
}
}
public extension String {
func contains(query: String, caseSensitive: Bool = false) -> Bool {
if query.isEmpty { return true }
let (s, q) = caseSensitive ? (self, query) : (self.lowercased(), query.lowercased())
var chars = s.characters; let qchars = q.characters
while !chars.isEmpty {
if chars.starts(with: qchars) { return true }
chars.removeFirst()
}
return false
}
func prefixedBy(query: String, caseSensitive: Bool = false) -> Bool {
let (s, q) = caseSensitive ? (self, query) : (self.lowercased(), query.lowercased())
return s.characters.starts(with: q.characters)
}
}
internal extension Array {
func objectArrayFromDictionaryArray<T>(intializer:([String: Any])->T?) -> [T] {
var returnValue = [T]()
for object in self {
if let dictionary = object as? [String: Any] {
if let value = intializer(dictionary) {
returnValue.append(value)
}
}
}
return returnValue
}
}
+6 -12
View File
@@ -22,7 +22,7 @@
// THE SOFTWARE.
public struct File {
public let id: String?
public let created: Int?
public let name: String?
@@ -36,8 +36,6 @@ public struct File {
public let isExternal: Bool?
public let externalType: String?
public let size: Int?
public let url: String?
public let urlDownload: String?
public let urlPrivate: String?
public let urlPrivateDownload: String?
public let thumb64: String?
@@ -64,7 +62,7 @@ public struct File {
internal(set) public var comments = [String: Comment]()
internal(set) public var reactions = [String: Reaction]()
init?(file:[String: AnyObject]?) {
public init?(file:[String: Any]?) {
id = file?["id"] as? String
created = file?["created"] as? Int
name = file?["name"] as? String
@@ -78,8 +76,6 @@ public struct File {
isExternal = file?["is_external"] as? Bool
externalType = file?["external_type"] as? String
size = file?["size"] as? Int
url = file?["url"] as? String
urlDownload = file?["url_download"] as? String
urlPrivate = file?["url_private"] as? String
urlPrivateDownload = file?["url_private_download"] as? String
thumb64 = file?["thumb_64"] as? String
@@ -99,17 +95,17 @@ public struct File {
channels = file?["channels"] as? [String]
groups = file?["groups"] as? [String]
ims = file?["ims"] as? [String]
initialComment = Comment(comment: file?["initial_comment"] as? [String: AnyObject])
initialComment = Comment(comment: file?["initial_comment"] as? [String: Any])
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)
if let reactions = file?["reactions"] as? [Any] {
self.reactions = Reaction.reactionsFromArray(array: reactions)
}
}
init?(id:String?) {
internal init?(id:String?) {
self.id = id
created = nil
name = nil
@@ -122,8 +118,6 @@ public struct File {
isExternal = nil
externalType = nil
size = nil
url = nil
urlDownload = nil
urlPrivate = nil
urlPrivateDownload = nil
thumb64 = nil
+35 -16
View File
@@ -21,13 +21,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public enum ItemType: String {
case ChannelMessage = "C"
case PrivateGroupMessage = "G"
case File = "F"
case FileComments = "Fc"
}
public class Message {
public let type = "message"
@@ -39,7 +32,7 @@ public class Message {
internal(set) public var text: String?
public let botID: String?
public let username: String?
public let icons: [String: AnyObject]?
public let icons: [String: Any]?
public let deletedTs: String?
internal(set) var purpose: String?
internal(set) var topic: String?
@@ -53,8 +46,9 @@ public class Message {
public let comment: Comment?
public let file: File?
internal(set) public var reactions = [String: Reaction]()
internal(set) public var attachments: [Attachment]?
init?(message: [String: AnyObject]?) {
public init?(message: [String: Any]?) {
subtype = message?["subtype"] as? String
ts = message?["ts"] as? String
user = message?["user"] as? String
@@ -63,7 +57,7 @@ public class Message {
text = message?["text"] as? String
botID = message?["bot_id"] as? String
username = message?["username"] as? String
icons = message?["icons"] as? [String: AnyObject]
icons = message?["icons"] as? [String: Any]
deletedTs = message?["deleted_ts"] as? String
purpose = message?["purpose"] as? String
topic = message?["topic"] as? String
@@ -74,14 +68,39 @@ public class Message {
itemType = message?["item_type"] as? String
isStarred = message?["is_starred"] as? Bool
pinnedTo = message?["pinned_to"] as? [String]
comment = Comment(comment: message?["comment"] as? [String: AnyObject])
file = File(file: message?["file"] as? [String: AnyObject])
if let messageReactions = message?["reactions"] as? [[String: AnyObject]] {
for react in messageReactions {
let reaction = Reaction(reaction: react)
self.reactions[reaction!.name!] = reaction
comment = Comment(comment: message?["comment"] as? [String: Any])
file = File(file: message?["file"] as? [String: Any])
reactions = messageReactions(reactions: message?["reactions"] as? [Any])
attachments = (message?["attachments"] as? [Any])?.objectArrayFromDictionaryArray(intializer: {(attachment) -> Attachment? in
return Attachment(attachment: attachment)
})
}
internal init?(ts:String?) {
self.ts = ts
subtype = nil
user = nil
channel = nil
botID = nil
username = nil
icons = nil
deletedTs = nil
upload = nil
itemType = nil
comment = nil
file = nil
}
private func messageReactions(reactions: [Any]?) -> [String: Reaction] {
var returnValue = [String: Reaction]()
if let r = reactions {
for react in r {
if let reaction = Reaction(reaction: react as? [String: Any]), reactionName = reaction.name {
returnValue[reactionName] = reaction
}
}
}
return returnValue
}
}
+151
View File
@@ -0,0 +1,151 @@
//
// NetworkInterface.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import C7
import HTTPSClient
import Jay
internal struct NetworkInterface {
private let apiUrl = "https://slack.com/api/"
private let client: HTTPSClient.Client?
init() {
do {
self.client = try Client(uri: URI("https://slack.com"))
} catch {
self.client = nil
}
}
internal func request(endpoint: SlackAPIEndpoint, token: String, parameters: [String: Any]?, successClosure: ([String: Any])->Void, errorClosure: (SlackError)->Void) {
var requestString = "\(apiUrl)\(endpoint.rawValue)?token=\(token)"
if let params = parameters {
requestString += requestStringFromParameters(parameters: params)
}
do {
var response: Response?
response = try client?.get(requestString)
let data = try response?.body.becomeBuffer()
if let data = data {
let json = try Jay().jsonFromData(data.bytes)
if let result = json as? [String: Any] {
if (result["ok"] as? Bool == true) {
successClosure(result)
} else {
if let errorString = result["error"] as? String {
throw ErrorDispatcher.dispatch(error: errorString)
} else {
throw SlackError.UnknownError
}
}
}
}
} catch let error {
if let slackError = error as? SlackError {
errorClosure(slackError)
} else {
errorClosure(SlackError.UnknownError)
}
}
}
internal func uploadRequest(token: String, data: Data, parameters: [String: Any]?, successClosure: ([String: Any])->Void, errorClosure: (SlackError)->Void) {
var requestString = "\(apiUrl)\(SlackAPIEndpoint.FilesUpload.rawValue)?token=\(token)"
if let params = parameters {
requestString = requestString + requestStringFromParameters(parameters: params)
}
let boundaryConstant = randomBoundary()
let contentType:Header = ["multipart/form-data; boundary=" + boundaryConstant]
let boundaryStart = "--\(boundaryConstant)\r\n"
let boundaryEnd = "--\(boundaryConstant)--\r\n"
let contentDispositionString = "Content-Disposition: form-data; name=\"file\"; filename=\"\(parameters!["filename"])\"\r\n"
let contentTypeString = "Content-Type: \(parameters!["filetype"])\r\n\r\n"
var requestBodyData = Data()
requestBodyData.append(contentsOf: boundaryStart.data.bytes)
requestBodyData.append(contentsOf: contentDispositionString.data.bytes)
requestBodyData.append(contentsOf: contentTypeString.data.bytes)
requestBodyData.append(contentsOf: data)
requestBodyData.append(contentsOf: "\r\n".data.bytes)
requestBodyData.append(contentsOf: boundaryEnd.data.bytes)
let header: Headers = ["Content-Type":contentType]
do {
var response: Response?
response = try client?.post(requestString, headers: header, body: requestBodyData)
let data = try response?.body.becomeBuffer()
if let data = data {
let json = try Jay().jsonFromData(data.bytes)
if let result = json as? [String: Any] {
if (result["ok"] as? Bool == true) {
successClosure(result)
} else {
if let errorString = result["error"] as? String {
throw ErrorDispatcher.dispatch(error: errorString)
} else {
throw SlackError.UnknownError
}
}
}
}
} catch let error {
if let slackError = error as? SlackError {
errorClosure(slackError)
} else {
errorClosure(SlackError.UnknownError)
}
}
}
private func randomBoundary() -> String {
return "slackkit.boundary.\(arc4random())\(arc4random())"
}
private func requestStringFromParameters(parameters: [String: Any]) -> String {
var requestString = ""
for key in parameters.keys {
if let value = parameters[key] as? String {
do {
let encodedValue = try value.percentEncoded(allowing: .uriQueryAllowed)
requestString += "&\(key)=\(encodedValue)"
} catch _ {
print("Error encoding parameters.")
}
} else if let value = parameters[key] as? Int {
requestString += "&\(key)=\(value)"
} else if let value = parameters[key] as? Bool {
requestString += "&\(key)=\(value)"
}
}
return requestString
}
}
+732
View File
@@ -0,0 +1,732 @@
//
// SlackWebAPI.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import C7
import Jay
internal enum SlackAPIEndpoint: String {
case APITest = "api.test"
case AuthTest = "auth.test"
case ChannelsHistory = "channels.history"
case ChannelsInfo = "channels.info"
case ChannelsList = "channels.list"
case ChannelsMark = "channels.mark"
case ChannelsSetPurpose = "channels.setPurpose"
case ChannelsSetTopic = "channels.setTopic"
case ChatDelete = "chat.delete"
case ChatPostMessage = "chat.postMessage"
case ChatUpdate = "chat.update"
case DNDInfo = "dnd.info"
case DNDTeamInfo = "dnd.teamInfo"
case EmojiList = "emoji.list"
case FilesCommentsAdd = "files.comments.add"
case FilesCommentsEdit = "files.comments.edit"
case FilesCommentsDelete = "files.comments.delete"
case FilesDelete = "files.delete"
case FilesUpload = "files.upload"
case GroupsClose = "groups.close"
case GroupsHistory = "groups.history"
case GroupsInfo = "groups.info"
case GroupsList = "groups.list"
case GroupsMark = "groups.mark"
case GroupsOpen = "groups.open"
case GroupsSetPurpose = "groups.setPurpose"
case GroupsSetTopic = "groups.setTopic"
case IMClose = "im.close"
case IMHistory = "im.history"
case IMList = "im.list"
case IMMark = "im.mark"
case IMOpen = "im.open"
case MPIMClose = "mpim.close"
case MPIMHistory = "mpim.history"
case MPIMList = "mpim.list"
case MPIMMark = "mpim.mark"
case MPIMOpen = "mpim.open"
case PinsAdd = "pins.add"
case PinsRemove = "pins.remove"
case ReactionsAdd = "reactions.add"
case ReactionsGet = "reactions.get"
case ReactionsList = "reactions.list"
case ReactionsRemove = "reactions.remove"
case RTMStart = "rtm.start"
case StarsAdd = "stars.add"
case StarsRemove = "stars.remove"
case TeamInfo = "team.info"
case UsersGetPresence = "users.getPresence"
case UsersInfo = "users.info"
case UsersList = "users.list"
case UsersSetActive = "users.setActive"
case UsersSetPresence = "users.setPresence"
}
public class SlackWebAPI {
public typealias FailureClosure = (error: SlackError)->Void
public enum InfoType: String {
case Purpose = "purpose"
case Topic = "topic"
}
public enum ParseMode: String {
case Full = "full"
case None = "none"
}
public enum Presence: String {
case Auto = "auto"
case Away = "away"
}
private enum ChannelType: String {
case Channel = "channel"
case Group = "group"
case IM = "im"
}
private let networkInterface: NetworkInterface
private let token: String
init(networkInterface: NetworkInterface, token: String) {
self.networkInterface = networkInterface
self.token = token
}
convenience public init(slackClient: SlackClient) {
self.init(networkInterface: slackClient.api, token: slackClient.token)
}
//MARK: - RTM
public func rtmStart(simpleLatest: Bool? = nil, noUnreads: Bool? = nil, mpimAware: Bool? = nil, success: ((response: [String: Any])->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["simple_latest": simpleLatest, "no_unreads": noUnreads, "mpim_aware": mpimAware]
networkInterface.request(endpoint: .RTMStart, token: token, parameters: filterNilParameters(parameters: parameters), successClosure: {
(response) -> Void in
success?(response: response)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Auth Test
public func authenticationTest(success: ((authenticated: Bool)->Void)?, failure: FailureClosure?) {
networkInterface.request(endpoint: .AuthTest, token: token, parameters: nil, successClosure: {
(response) -> Void in
success?(authenticated: true)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Channels
public func channelHistory(id: String, latest: String = "\(Time.slackTimestamp())", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History?)->Void)?, failure: FailureClosure?) {
history(endpoint: .ChannelsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {
(history) -> Void in
success?(history: history)
}) {(error) -> Void in
failure?(error: error)
}
}
public func channelInfo(id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) {
info(endpoint: .ChannelsInfo, type:ChannelType.Channel, id: id, success: {
(channel) -> Void in
success?(channel: channel)
}) { (error) -> Void in
failure?(error: error)
}
}
public func channelsList(excludeArchived: Bool = false, success: ((channels: [Any]?)->Void)?, failure: FailureClosure?) {
list(endpoint: .ChannelsList, type:ChannelType.Channel, excludeArchived: excludeArchived, success: {
(channels) -> Void in
success?(channels: channels)
}) {(error) -> Void in
failure?(error: error)
}
}
public func markChannel(channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) {
mark(endpoint: .ChannelsMark, channel: channel, timestamp: timestamp, success: {
(ts) -> Void in
success?(ts:timestamp)
}) {(error) -> Void in
failure?(error: error)
}
}
public func setChannelPurpose(channel: String, purpose: String, success: ((purposeSet: Bool)->Void)?, failure: FailureClosure?) {
setInfo(endpoint: .ChannelsSetPurpose, type: .Purpose, channel: channel, text: purpose, success: {
(purposeSet) -> Void in
success?(purposeSet: purposeSet)
}) { (error) -> Void in
failure?(error: error)
}
}
public func setChannelTopic(channel: String, topic: String, success: ((topicSet: Bool)->Void)?, failure: FailureClosure?) {
setInfo(endpoint: .ChannelsSetTopic, type: .Topic, channel: channel, text: topic, success: {
(topicSet) -> Void in
success?(topicSet: topicSet)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Messaging
public func deleteMessage(channel: String, ts: String, success: ((deleted: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["channel": channel, "ts": ts]
networkInterface.request(endpoint: .ChatDelete, token: token, parameters: parameters, successClosure: { (response) -> Void in
success?(deleted: true)
}) {(error) -> Void in
failure?(error: error)
}
}
public func sendMessage(channel: String, text: String, 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, "text":text.slackFormatEscaping(), "as_user":asUser, "parse":parse?.rawValue, "link_names":linkNames, "unfurl_links":unfurlLinks, "unfurlMedia":unfurlMedia, "username":username, "attachments":encodeAttachments(attachments: attachments), "icon_url":iconURL, "icon_emoji":iconEmoji]
networkInterface.request(endpoint: .ChatPostMessage, token: token, parameters: filterNilParameters(parameters: parameters), successClosure: {
(response) -> Void in
success?((ts: response["ts"] as? String, response["channel"] as? String))
}) {(error) -> Void in
failure?(error: error)
}
}
public func updateMessage(channel: String, ts: String, message: String, attachments: [Attachment?]? = nil, parse:ParseMode = .None, linkNames: Bool = false, success: ((updated: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["channel": channel, "ts": ts, "text": message.slackFormatEscaping(), "parse": parse.rawValue, "link_names": linkNames, "attachments":encodeAttachments(attachments: attachments)]
networkInterface.request(endpoint: .ChatUpdate, token: token, parameters: filterNilParameters(parameters: parameters), successClosure: {
(response) -> Void in
success?(updated: true)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Do Not Disturb
public func dndInfo(user: String? = nil, success: ((status: DoNotDisturbStatus?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["user": user]
networkInterface.request(endpoint: .DNDInfo, token: token, parameters: filterNilParameters(parameters: parameters), successClosure: {
(response) -> Void in
success?(status: DoNotDisturbStatus(status: response))
}) {(error) -> Void in
failure?(error: error)
}
}
public func dndTeamInfo(users: [String]? = nil, success: ((statuses: [String: DoNotDisturbStatus]?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["users":users?.joined(separator: ",")]
networkInterface.request(endpoint: .DNDTeamInfo, token: token, parameters: filterNilParameters(parameters: parameters), successClosure: {
(response) -> Void in
success?(statuses: self.enumerateDNDStauses(statuses: response["users"] as? [String: Any]))
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Emoji
public func emojiList(success: ((emojiList: [String: Any]?)->Void)?, failure: FailureClosure?) {
networkInterface.request(endpoint: .EmojiList, token: token, parameters: nil, successClosure: {
(response) -> Void in
success?(emojiList: response["emoji"] as? [String: Any])
}) { (error) -> Void in
failure?(error: error)
}
}
//MARK: - Files
public func deleteFile(fileID: String, success: ((deleted: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["file":fileID]
networkInterface.request(endpoint: .FilesDelete, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(deleted: true)
}) {(error) -> Void in
failure?(error: error)
}
}
public func uploadFile(file: Data, filename: String, filetype: String = "auto", title: String? = nil, initialComment: String? = nil, channels: [String]? = nil, success: ((file: File?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["file":file, "filename": filename, "filetype":filetype, "title":title, "initial_comment":initialComment, "channels":channels?.joined(separator: ",")]
networkInterface.uploadRequest(token: token, data: file, parameters: filterNilParameters(parameters: parameters), successClosure: {
(response) -> Void in
success?(file: File(file: response["file"] as? [String: Any]))
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - File Comments
public func addFileComment(fileID: String, comment: String, success: ((comment: Comment?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["file":fileID, "comment":comment.slackFormatEscaping()]
networkInterface.request(endpoint: .FilesCommentsAdd, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(comment: Comment(comment: response["comment"] as? [String: Any]))
}) {(error) -> Void in
failure?(error: error)
}
}
public func editFileComment(fileID: String, commentID: String, comment: String, success: ((comment: Comment?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["file":fileID, "id":commentID, "comment":comment.slackFormatEscaping()]
networkInterface.request(endpoint: .FilesCommentsEdit, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(comment: Comment(comment: response["comment"] as? [String: Any]))
}) {(error) -> Void in
failure?(error: error)
}
}
public func deleteFileComment(fileID: String, commentID: String, success: ((deleted: Bool?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["file":fileID, "id": commentID]
networkInterface.request(endpoint: .FilesCommentsDelete, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(deleted: true)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Groups
public func closeGroup(groupID: String, success: ((closed: Bool)->Void)?, failure: FailureClosure?) {
close(endpoint: .GroupsClose, channelID: groupID, success: {
(closed) -> Void in
success?(closed:closed)
}) {(error) -> Void in
failure?(error:error)
}
}
public func groupHistory(id: String, latest: String = "\(Time.slackTimestamp())", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History?)->Void)?, failure: FailureClosure?) {
history(endpoint: .GroupsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {
(history) -> Void in
success?(history: history)
}) {(error) -> Void in
failure?(error: error)
}
}
public func groupInfo(id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) {
info(endpoint: .GroupsInfo, type:ChannelType.Group, id: id, success: {
(channel) -> Void in
success?(channel: channel)
}) {(error) -> Void in
failure?(error: error)
}
}
public func groupsList(excludeArchived: Bool = false, success: ((channels: [Any]?)->Void)?, failure: FailureClosure?) {
list(endpoint: .GroupsList, type:ChannelType.Group, excludeArchived: excludeArchived, success: {
(channels) -> Void in
success?(channels: channels)
}) {(error) -> Void in
failure?(error: error)
}
}
public func markGroup(channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) {
mark(endpoint: .GroupsMark, channel: channel, timestamp: timestamp, success: {
(ts) -> Void in
success?(ts: timestamp)
}) {(error) -> Void in
failure?(error: error)
}
}
public func openGroup(channel: String, success: ((opened: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["channel":channel]
networkInterface.request(endpoint: .GroupsOpen, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(opened: true)
}) {(error) -> Void in
failure?(error: error)
}
}
public func setGroupPurpose(channel: String, purpose: String, success: ((purposeSet: Bool)->Void)?, failure: FailureClosure?) {
setInfo(endpoint: .GroupsSetPurpose, type: .Purpose, channel: channel, text: purpose, success: {
(purposeSet) -> Void in
success?(purposeSet: purposeSet)
}) {(error) -> Void in
failure?(error: error)
}
}
public func setGroupTopic(channel: String, topic: String, success: ((topicSet: Bool)->Void)?, failure: FailureClosure?) {
setInfo(endpoint: .GroupsSetTopic, type: .Topic, channel: channel, text: topic, success: {
(topicSet) -> Void in
success?(topicSet: topicSet)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - IM
public func closeIM(channel: String, success: ((closed: Bool)->Void)?, failure: FailureClosure?) {
close(endpoint: .IMClose, channelID: channel, success: {
(closed) -> Void in
success?(closed: closed)
}) {(error) -> Void in
failure?(error: error)
}
}
public func imHistory(id: String, latest: String = "\(Time.slackTimestamp())", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History?)->Void)?, failure: FailureClosure?) {
history(endpoint: .IMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {
(history) -> Void in
success?(history: history)
}) {(error) -> Void in
failure?(error: error)
}
}
public func imsList(excludeArchived: Bool = false, success: ((channels: [Any]?)->Void)?, failure: FailureClosure?) {
list(endpoint: .IMList, type:ChannelType.IM, excludeArchived: excludeArchived, success: {
(channels) -> Void in
success?(channels: channels)
}) {(error) -> Void in
failure?(error: error)
}
}
public func markIM(channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) {
mark(endpoint: .IMMark, channel: channel, timestamp: timestamp, success: {
(ts) -> Void in
success?(ts: timestamp)
}) {(error) -> Void in
failure?(error: error)
}
}
public func openIM(userID: String, success: ((imID: String?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["user":userID]
networkInterface.request(endpoint: .IMOpen, token: token, parameters: parameters, successClosure: {
(response) -> Void in
let group = response["channel"] as? [String: Any]
success?(imID: group?["id"] as? String)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - MPIM
public func closeMPIM(channel: String, success: ((closed: Bool)->Void)?, failure: FailureClosure?) {
close(endpoint: .MPIMClose, channelID: channel, success: {
(closed) -> Void in
success?(closed: closed)
}) {(error) -> Void in
failure?(error: error)
}
}
public func mpimHistory(id: String, latest: String = "\(Time.slackTimestamp())", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History?)->Void)?, failure: FailureClosure?) {
history(endpoint: .MPIMHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {
(history) -> Void in
success?(history: history)
}) {(error) -> Void in
failure?(error: error)
}
}
public func mpimsList(excludeArchived: Bool = false, success: ((channels: [Any]?)->Void)?, failure: FailureClosure?) {
list(endpoint: .MPIMList, type:ChannelType.Group, excludeArchived: excludeArchived, success: {
(channels) -> Void in
success?(channels: channels)
}) {(error) -> Void in
failure?(error: error)
}
}
public func markMPIM(channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) {
mark(endpoint: .MPIMMark, channel: channel, timestamp: timestamp, success: {
(ts) -> Void in
success?(ts: timestamp)
}) {(error) -> Void in
failure?(error: error)
}
}
public func openMPIM(userIDs: [String], success: ((mpimID: String?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["users":userIDs.joined(separator: ",")]
networkInterface.request(endpoint: .MPIMOpen, token: token, parameters: parameters, successClosure: {
(response) -> Void in
let group = response["group"] as? [String: Any]
success?(mpimID: group?["id"] as? String)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Pins
public func pinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: ((pinned: Bool)->Void)?, failure: FailureClosure?) {
pin(endpoint: .PinsAdd, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: {
(ok) -> Void in
success?(pinned: ok)
}) {(error) -> Void in
failure?(error: error)
}
}
public func unpinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: ((unpinned: Bool)->Void)?, failure: FailureClosure?) {
pin(endpoint: .PinsRemove, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: {
(ok) -> Void in
success?(unpinned: ok)
}) {(error) -> Void in
failure?(error: error)
}
}
private func pin(endpoint: SlackAPIEndpoint, channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: ((ok: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["channel":channel, "file":file, "file_comment":fileComment, "timestamp":timestamp]
networkInterface.request(endpoint: endpoint, token: token, parameters: filterNilParameters(parameters: parameters), successClosure: {
(response) -> Void in
success?(ok: true)
}){(error) -> Void in
failure?(error: error)
}
}
//MARK: - Reactions
// One of file, file_comment, or the combination of channel and timestamp must be specified.
public func addReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((reacted: Bool)->Void)?, failure: FailureClosure?) {
react(endpoint: .ReactionsAdd, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {
(ok) -> Void in
success?(reacted: ok)
}) {(error) -> Void in
failure?(error: error)
}
}
// One of file, file_comment, or the combination of channel and timestamp must be specified.
public func removeReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((unreacted: Bool)->Void)?, failure: FailureClosure?) {
react(endpoint: .ReactionsRemove, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {
(ok) -> Void in
success?(unreacted: ok)
}) {(error) -> Void in
failure?(error: error)
}
}
private func react(endpoint: SlackAPIEndpoint, name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((ok: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["name":name, "file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp]
networkInterface.request(endpoint: endpoint, token: token, parameters: filterNilParameters(parameters: parameters), successClosure: {
(response) -> Void in
success?(ok: true)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Stars
// One of file, file_comment, channel, or the combination of channel and timestamp must be specified.
public func addStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((starred: Bool)->Void)?, failure: FailureClosure?) {
star(endpoint: .StarsAdd, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {
(ok) -> Void in
success?(starred: ok)
}) {(error) -> Void in
failure?(error: error)
}
}
// One of file, file_comment, channel, or the combination of channel and timestamp must be specified.
public func removeStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((unstarred: Bool)->Void)?, failure: FailureClosure?) {
star(endpoint: .StarsRemove, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {
(ok) -> Void in
success?(unstarred: ok)
}) {(error) -> Void in
failure?(error: error)
}
}
private func star(endpoint: SlackAPIEndpoint, file: String?, fileComment: String?, channel: String?, timestamp: String?, success: ((ok: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["file":file, "file_comment":fileComment, "channel":channel, "timestamp":timestamp]
networkInterface.request(endpoint: endpoint, token: token, parameters: filterNilParameters(parameters: parameters), successClosure: {
(response) -> Void in
success?(ok: true)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Team
public func teamInfo(success: ((info: [String: Any]?)->Void)?, failure: FailureClosure?) {
networkInterface.request(endpoint: .TeamInfo, token: token, parameters: nil, successClosure: {
(response) -> Void in
success?(info: response["team"] as? [String: Any])
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Users
public func userPresence(user: String, success: ((presence: String?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["user":user]
networkInterface.request(endpoint: .UsersGetPresence, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(presence: response["presence"] as? String)
}){(error) -> Void in
failure?(error: error)
}
}
public func userInfo(id: String, success: ((user: User?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["user":id]
networkInterface.request(endpoint: .UsersInfo, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(user: User(user: response["user"] as? [String: Any]))
}) {(error) -> Void in
failure?(error: error)
}
}
public func usersList(includePresence: Bool = false, success: ((userList: [String: Any]?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["presence":includePresence]
networkInterface.request(endpoint: .UsersList, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(userList: response["members"] as? [String: Any])
}){(error) -> Void in
failure?(error: error)
}
}
public func setUserActive(success: ((success: Bool)->Void)?, failure: FailureClosure?) {
networkInterface.request(endpoint: .UsersSetActive, token: token, parameters: nil, successClosure: {
(response) -> Void in
success?(success: true)
}) {(error) -> Void in
failure?(error: error)
}
}
public func setUserPresence(presence: Presence, success: ((success: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["presence":presence.rawValue]
networkInterface.request(endpoint: .UsersSetPresence, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(success:true)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Channel Utilities
private func close(endpoint: SlackAPIEndpoint, channelID: String, success: ((closed: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["channel":channelID]
networkInterface.request(endpoint: endpoint, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(closed: true)
}) {(error) -> Void in
failure?(error: error)
}
}
private func history(endpoint: SlackAPIEndpoint, id: String, latest: String = "\(Time.slackTimestamp())", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((history: History?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["channel": id, "latest": latest, "oldest": oldest, "inclusive":inclusive, "count":count, "unreads":unreads]
networkInterface.request(endpoint: endpoint, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(history: History(history: response))
}) {(error) -> Void in
failure?(error: error)
}
}
private func info(endpoint: SlackAPIEndpoint, type: ChannelType, id: String, success: ((channel: Channel?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["channel": id]
networkInterface.request(endpoint: endpoint, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(channel: Channel(channel: response[type.rawValue] as? [String: Any]))
}) {(error) -> Void in
failure?(error: error)
}
}
private func list(endpoint: SlackAPIEndpoint, type: ChannelType, excludeArchived: Bool = false, success: ((channels: [Any]?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["exclude_archived": excludeArchived]
networkInterface.request(endpoint: endpoint, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(channels: response[type.rawValue+"s"] as? [Any])
}) {(error) -> Void in
failure?(error: error)
}
}
private func mark(endpoint: SlackAPIEndpoint, channel: String, timestamp: String, success: ((ts: String)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["channel": channel, "ts": timestamp]
networkInterface.request(endpoint: endpoint, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(ts: timestamp)
}) {(error) -> Void in
failure?(error: error)
}
}
private func setInfo(endpoint: SlackAPIEndpoint, type: InfoType, channel: String, text: String, success: ((success: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["channel": channel, type.rawValue: text]
networkInterface.request(endpoint: endpoint, token: token, parameters: parameters, successClosure: {
(response) -> Void in
success?(success: true)
}) {(error) -> Void in
failure?(error: error)
}
}
//MARK: - Filter Nil Parameters
private func filterNilParameters(parameters: [String: Any?]) -> [String: Any] {
var finalParameters = [String: Any]()
for key in parameters.keys {
if parameters[key] != nil {
finalParameters[key] = parameters[key]!
}
}
return finalParameters
}
//MARK: - Encode Attachments
private func encodeAttachments(attachments: [Attachment?]?) -> String? {
if let attachments = attachments {
var attachmentArray: [Any] = []
for attachment in attachments {
if let attachment = attachment {
attachmentArray.append(attachment.dictionary())
}
}
do {
let string = try Jay().dataFromJson(attachmentArray).string()
return string
} catch _ {
}
}
return nil
}
//MARK: - Enumerate Do Not Distrub Status
private func enumerateDNDStauses(statuses: [String: Any]?) -> [String: DoNotDisturbStatus] {
var retVal = [String: DoNotDisturbStatus]()
if let keys = statuses?.keys {
for key in keys {
retVal[key] = DoNotDisturbStatus(status: statuses?[key] as? [String: Any])
}
}
return retVal
}
}
@@ -0,0 +1,288 @@
//
// SlackWebAPIErrorHandling.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public enum SlackError: ErrorProtocol {
case AccountInactive
case AlreadyArchived
case AlreadyInChannel
case AlreadyPinned
case AlreadyReacted
case AlreadyStarred
case BadClientSecret
case BadRedirectURI
case BadTimeStamp
case CantArchiveGeneral
case CantDelete
case CantDeleteFile
case CantDeleteMessage
case CantInvite
case CantInviteSelf
case CantKickFromGeneral
case CantKickFromLastChannel
case CantKickSelf
case CantLeaveGeneral
case CantLeaveLastChannel
case CantUpdateMessage
case ChannelNotFound
case ComplianceExportsPreventDeletion
case EditWindowClosed
case FileCommentNotFound
case FileDeleted
case FileNotFound
case FileNotShared
case GroupContainsOthers
case InvalidArrayArg
case InvalidAuth
case InvalidChannel
case InvalidCharSet
case InvalidClientID
case InvalidCode
case InvalidFormData
case InvalidName
case InvalidPostType
case InvalidPresence
case InvalidTS
case InvalidTSLatest
case InvalidTSOldest
case IsArchived
case LastMember
case LastRAChannel
case MessageNotFound
case MessageTooLong
case MigrationInProgress
case MissingDuration
case MissingPostType
case NameTaken
case NoChannel
case NoComment
case NoItemSpecified
case NoReaction
case NoText
case NotArchived
case NotAuthed
case NotEnoughUsers
case NotInChannel
case NotInGroup
case NotPinned
case NotStarred
case OverPaginationLimit
case PaidOnly
case PermissionDenied
case PostingToGeneralChannelDenied
case RateLimited
case RequestTimeout
case RestrictedAction
case SnoozeEndFailed
case SnoozeFailed
case SnoozeNotActive
case TooLong
case TooManyEmoji
case TooManyReactions
case TooManyUsers
case UnknownError
case UnknownType
case UserDisabled
case UserDoesNotOwnChannel
case UserIsBot
case UserIsRestricted
case UserIsUltraRestricted
case UserListNotSupplied
case UserNotFound
case UserNotVisible
}
internal struct ErrorDispatcher {
static func dispatch(error: String) -> SlackError {
switch error {
case "account_inactive":
return .AccountInactive
case "already_in_channel":
return .AlreadyInChannel
case "already_pinned":
return .AlreadyPinned
case "already_reacted":
return .AlreadyReacted
case "already_starred":
return .AlreadyStarred
case "bad_client_secret":
return .BadClientSecret
case "bad_redirect_uri":
return .BadRedirectURI
case "bad_timestamp":
return .BadTimeStamp
case "cant_delete":
return .CantDelete
case "cant_delete_file":
return .CantDeleteFile
case "cant_delete_message":
return .CantDeleteMessage
case "cant_invite":
return .CantInvite
case "cant_invite_self":
return .CantInviteSelf
case "cant_kick_from_general":
return .CantKickFromGeneral
case "cant_kick_from_last_channel":
return .CantKickFromLastChannel
case "cant_kick_self":
return .CantKickSelf
case "cant_leave_general":
return .CantLeaveGeneral
case "cant_leave_last_channel":
return .CantLeaveLastChannel
case "cant_update_message":
return .CantUpdateMessage
case "compliance_exports_prevent_deletion":
return .ComplianceExportsPreventDeletion
case "channel_not_found":
return .ChannelNotFound
case "edit_window_closed":
return .EditWindowClosed
case "file_comment_not_found":
return .FileCommentNotFound
case "file_deleted":
return .FileDeleted
case "file_not_found":
return .FileNotFound
case "file_not_shared":
return .FileNotShared
case "group_contains_others":
return .GroupContainsOthers
case "invalid_array_arg":
return .InvalidArrayArg
case "invalid_auth":
return .InvalidAuth
case "invalid_channel":
return .InvalidChannel
case "invalid_charset":
return .InvalidCharSet
case "invalid_client_id":
return .InvalidClientID
case "invalid_code":
return .InvalidCode
case "invalid_form_data":
return .InvalidFormData
case "invalid_name":
return .InvalidName
case "invalid_post_type":
return .InvalidPostType
case "invalid_presence":
return .InvalidPresence
case "invalid_timestamp":
return .InvalidTS
case "invalid_ts_latest":
return .InvalidTSLatest
case "invalid_ts_oldest":
return .InvalidTSOldest
case "is_archived":
return .IsArchived
case "last_member":
return .LastMember
case "last_ra_channel":
return .LastRAChannel
case "message_not_found":
return .MessageNotFound
case "msg_too_long":
return .MessageTooLong
case "migration_in_progress":
return .MigrationInProgress
case "missing_duration":
return .MissingDuration
case "missing_post_type":
return .MissingPostType
case "name_taken":
return .NameTaken
case "no_channel":
return .NoChannel
case "no_comment":
return .NoComment
case "no_reaction":
return .NoReaction
case "no_item_specified":
return .NoItemSpecified
case "no_text":
return .NoText
case "not_archived":
return .NotArchived
case "not_authed":
return .NotAuthed
case "not_enough_users":
return .NotEnoughUsers
case "not_in_channel":
return .NotInChannel
case "not_in_group":
return .NotInGroup
case "not_pinned":
return .NotPinned
case "not_starred":
return .NotStarred
case "over_pagination_limit":
return .OverPaginationLimit
case "paid_only":
return .PaidOnly
case "perimssion_denied":
return .PermissionDenied
case "posting_to_general_channel_denied":
return .PostingToGeneralChannelDenied
case "rate_limited":
return .RateLimited
case "request_timeout":
return .RequestTimeout
case "snooze_end_failed":
return .SnoozeEndFailed
case "snooze_failed":
return .SnoozeFailed
case "snooze_not_active":
return .SnoozeNotActive
case "too_long":
return .TooLong
case "too_many_emoji":
return .TooManyEmoji
case "too_many_reactions":
return .TooManyReactions
case "too_many_users":
return .TooManyUsers
case "unknown_type":
return .UnknownType
case "user_disabled":
return .UserDisabled
case "user_does_not_own_channel":
return .UserDoesNotOwnChannel
case "user_is_bot":
return .UserIsBot
case "user_is_restricted":
return .UserIsRestricted
case "user_is_ultra_restricted":
return .UserIsUltraRestricted
case "user_list_not_supplied":
return .UserListNotSupplied
case "user_not_found":
return .UserNotFound
case "user_not_visible":
return .UserNotVisible
default:
return .UnknownError
}
}
}
+32 -7
View File
@@ -25,21 +25,46 @@ public struct Team {
public let id: String
internal(set) public var name: String?
internal(set) public var emailDomain: String?
internal(set) public var domain: String?
internal(set) public var emailDomain: String?
internal(set) public var messageEditWindowMinutes: Int?
internal(set) public var overStorageLimit: Bool?
internal(set) public var prefs: [String: AnyObject]?
internal(set) public var prefs: [String: Any]?
internal(set) public var plan: String?
internal(set) public var icon: TeamIcon?
internal init?(team: [String: AnyObject]?) {
internal init?(team: [String: Any]?) {
id = team?["id"] as! String
name = team?["id"] as? String
emailDomain = team?["email_domain"] as? String
name = team?["name"] as? String
domain = team?["domain"] as? String
messageEditWindowMinutes = team?["mesg_edit_window_mins"] as? Int
emailDomain = team?["email_domain"] as? String
messageEditWindowMinutes = team?["msg_edit_window_mins"] as? Int
overStorageLimit = team?["over_storage_limit"] as? Bool
prefs = team?["prefs"] as? [String: AnyObject]
prefs = team?["prefs"] as? [String: Any]
plan = team?["plan"] as? String
icon = TeamIcon(icon: team?["icon"] as? [String: Any])
}
}
public struct TeamIcon {
internal(set) public var image34: String?
internal(set) public var image44: String?
internal(set) public var image68: String?
internal(set) public var image88: String?
internal(set) public var image102: String?
internal(set) public var image132: String?
internal(set) public var imageOriginal: String?
internal(set) public var imageDefault: Bool?
internal init?(icon: [String: Any]?) {
image34 = icon?["image_34"] as? String
image44 = icon?["image_44"] as? String
image68 = icon?["image_68"] as? String
image88 = icon?["image_88"] as? String
image102 = icon?["image_102"] as? String
image132 = icon?["image_132"] as? String
imageOriginal = icon?["image_original"] as? String
imageDefault = icon?["image_default"] as? Bool
}
}
+116 -19
View File
@@ -21,23 +21,45 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// MARK: - Edited
public struct Edited {
public let user: String?
public let ts: String?
internal init?(edited:[String: AnyObject]?) {
internal init?(edited:[String: Any]?) {
user = edited?["user"] as? String
ts = edited?["ts"] as? String
}
}
// MARK: - History
public struct History {
internal(set) public var latest: Double?
internal(set) public var messages = [Message]()
public let hasMore: Bool?
internal init?(history: [String: Any]?) {
if let latestStr = history?["latest"] as? String, latestDouble = Double(latestStr) {
latest = latestDouble
}
if let msgs = history?["messages"] as? [Any] {
for message in msgs {
if let message = Message(message: message as? [String: Any]) {
messages.append(message)
}
}
}
hasMore = history?["has_more"] as? Bool
}
}
// MARK: - Reaction
public struct Reaction {
public let name: String?
internal(set) public var users = [String: String]()
internal init?(reaction:[String: AnyObject]?) {
internal init?(reaction:[String: Any]?) {
name = reaction?["name"] as? String
}
@@ -51,17 +73,19 @@ public struct Reaction {
self.users = users
}
static func reactionsFromArray(array: [[String: AnyObject]]) -> [String: Reaction] {
static func reactionsFromArray(array: [Any]) -> [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
for r in array {
if let reaction = r as? [String: Any] {
if let users = reaction["users"] as? [String] {
for user in users {
userDictionary[user] = user
}
}
if let name = reaction["name"] as? String {
reactions[name] = Reaction(name: name, users: userDictionary)
}
}
if let name = reaction["name"] as? String {
reactions[name] = Reaction(name: name, users: userDictionary)
}
}
return reactions
@@ -85,7 +109,7 @@ public struct Comment {
internal(set) public var stars: Int?
internal(set) public var reactions = [String: Reaction]()
internal init?(comment:[String: AnyObject]?) {
internal init?(comment:[String: Any]?) {
id = comment?["id"] as? String
created = comment?["created"] as? Int
user = comment?["user"] as? String
@@ -116,23 +140,23 @@ public struct Item {
public let comment: Comment?
public let fileCommentID: String?
internal init?(item:[String: AnyObject]?) {
internal init?(item:[String: Any]?) {
type = item?["type"] as? String
ts = item?["ts"] as? String
channel = item?["channel"] as? String
message = Message(message: item?["message"] as? [String: AnyObject])
message = Message(message: item?["message"] as? [String: Any])
// Comment and File can come across as Strings or Dictionaries
if (Comment(comment: item?["comment"] as? [String: AnyObject])?.id == nil) {
if (Comment(comment: item?["comment"] as? [String: Any])?.id == nil) {
comment = Comment(id: item?["comment"] as? String)
} else {
comment = Comment(comment: item?["comment"] as? [String: AnyObject])
comment = Comment(comment: item?["comment"] as? [String: Any])
}
if (File(file: item?["file"] as? [String: AnyObject])?.id == nil) {
if (File(file: item?["file"] as? [String: Any])?.id == nil) {
file = File(id: item?["file"] as? String)
} else {
file = File(file: item?["file"] as? [String: AnyObject])
file = File(file: item?["file"] as? [String: Any])
}
fileCommentID = item?["file_comment"] as? String
@@ -151,7 +175,7 @@ public struct Topic {
public let creator: String?
public let lastSet: Int?
internal init?(topic: [String: AnyObject]?) {
internal init?(topic: [String: Any]?) {
value = topic?["value"] as? String
creator = topic?["creator"] as? String
lastSet = topic?["last_set"] as? Int
@@ -166,7 +190,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: Any]?) {
enabled = status?["dnd_enabled"] as? Bool
nextDoNotDisturbStart = status?["next_dnd_start_ts"] as? Int
nextDoNotDisturbEnd = status?["next_dnd_end_ts"] as? Int
@@ -175,3 +199,76 @@ public struct DoNotDisturbStatus {
}
}
// MARK - Custom Team Profile
public struct CustomProfile {
internal(set) public var fields = [String: CustomProfileField]()
internal init?(profile: [String: Any]?) {
if let eventFields = profile?["fields"] as? [Any] {
for field in eventFields {
if let cpf = CustomProfileField(field: field as? [String: Any]), id = cpf.id {
fields[id] = cpf
} else {
if let cpf = CustomProfileField(id: field as? String), id = cpf.id {
fields[id] = cpf
}
}
}
}
}
internal init?(customFields: [String: Any]?) {
if let customFields = customFields {
for key in customFields.keys {
if let cpf = CustomProfileField(field: customFields[key] as? [String: Any]) {
self.fields[key] = cpf
}
}
}
}
}
public struct CustomProfileField {
internal(set) public var id: String?
internal(set) public var alt: String?
internal(set) public var value: String?
internal(set) public var hidden: Bool?
internal(set) public var hint: String?
internal(set) public var label: String?
internal(set) public var options: String?
internal(set) public var ordering: Int?
internal(set) public var possibleValues: [String]?
internal(set) public var type: String?
internal init?(field: [String: Any]?) {
id = field?["id"] as? String
alt = field?["alt"] as? String
value = field?["value"] as? String
hidden = field?["is_hidden"] as? Bool
hint = field?["hint"] as? String
label = field?["label"] as? String
options = field?["options"] as? String
ordering = field?["ordering"] as? Int
possibleValues = field?["possible_values"] as? [String]
type = field?["type"] as? String
}
internal init?(id: String?) {
self.id = id
}
internal mutating func updateProfileField(profile: CustomProfileField?) {
id = profile?.id != nil ? profile?.id : id
alt = profile?.alt != nil ? profile?.alt : alt
value = profile?.value != nil ? profile?.value : value
hidden = profile?.hidden != nil ? profile?.hidden : hidden
hint = profile?.hint != nil ? profile?.hint : hint
label = profile?.label != nil ? profile?.label : label
options = profile?.options != nil ? profile?.options : options
ordering = profile?.ordering != nil ? profile?.ordering : ordering
possibleValues = profile?.possibleValues != nil ? profile?.possibleValues : possibleValues
type = profile?.type != nil ? profile?.type : type
}
}
+10 -7
View File
@@ -35,8 +35,9 @@ public struct User {
internal(set) public var image48: String?
internal(set) public var image72: String?
internal(set) public var image192: String?
internal(set) public var customProfile: CustomProfile?
internal init?(profile: [String: AnyObject]?) {
internal init?(profile: [String: Any]?) {
firstName = profile?["first_name"] as? String
lastName = profile?["last_name"] as? String
realName = profile?["real_name"] as? String
@@ -48,9 +49,11 @@ public struct User {
image48 = profile?["image_48"] as? String
image72 = profile?["image_72"] as? String
image192 = profile?["image_192"] as? String
customProfile = CustomProfile(customFields: profile?["fields"] as? [String: Any])
}
}
public let id: String?
internal(set) public var name: String?
internal(set) public var deleted: Bool?
@@ -70,15 +73,15 @@ public struct User {
internal(set) public var timeZone: String?
internal(set) public var timeZoneLabel: String?
internal(set) public var timeZoneOffSet: Int?
internal(set) public var preferences: [String: AnyObject]?
// Client use
internal(set) public var preferences: [String: Any]?
// Client properties
internal(set) public var userGroups: [String: String]?
internal init?(user: [String: AnyObject]?) {
internal init?(user: [String: Any]?) {
id = user?["id"] as? String
name = user?["name"] as? String
deleted = user?["deleted"] as? Bool
profile = Profile(profile: user?["profile"] as? [String: AnyObject])
profile = Profile(profile: user?["profile"] as? [String: Any])
color = user?["color"] as? String
isAdmin = user?["is_admin"] as? Bool
isOwner = user?["is_owner"] as? Bool
@@ -93,11 +96,11 @@ public struct User {
timeZone = user?["tz"] as? String
timeZoneLabel = user?["tz_label"] as? String
timeZoneOffSet = user?["tz_offset"] as? Int
preferences = user?["prefs"] as? [String: AnyObject]
preferences = user?["prefs"] as? [String: Any]
}
internal init?(id: String?) {
self.id = id
self.isBot = nil
}
}
}
+3 -3
View File
@@ -39,11 +39,11 @@ public struct UserGroup {
public let createdBy: String?
internal(set) public var updatedBy: String?
internal(set) public var deletedBy: String?
internal(set) public var preferences: [String: AnyObject]?
internal(set) public var preferences: [String: Any]?
internal(set) public var users: [String]?
internal(set) public var userCount: Int?
internal init?(userGroup: [String: AnyObject]?) {
internal init?(userGroup: [String: Any]?) {
id = userGroup?["id"] as? String
teamID = userGroup?["team_id"] as? String
isUserGroup = userGroup?["is_usergroup"] as? Bool
@@ -58,7 +58,7 @@ public struct UserGroup {
createdBy = userGroup?["created_by"] as? String
updatedBy = userGroup?["updated_by"] as? String
deletedBy = userGroup?["deleted_by"] as? String
preferences = userGroup?["prefs"] as? [String: AnyObject]
preferences = userGroup?["prefs"] as? [String: Any]
users = userGroup?["users"] as? [String]
if let count = userGroup?["user_count"] as? String {
userCount = Int(count)