Compare commits

..

39 Commits

Author SHA1 Message Date
Peter Zignego 3d6516922f Swift verison and random fix 2016-06-16 14:25:49 -04:00
Peter Zignego 1b98af2b11 05-09 snapshot updates 2016-06-15 22:10:39 -04:00
Peter Zignego 750604a801 Update C7 version 2016-06-15 21:52:19 -04:00
Peter Zignego 47f5d040a2 Fix version 2016-06-15 21:49:34 -04:00
Peter Zignego 3c34557617 Update package 2016-06-15 21:46:07 -04:00
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
37 changed files with 4245 additions and 1122 deletions
+4 -6
View File
@@ -16,11 +16,9 @@ DerivedData
*.hmap
*.ipa
*.xcuserstate
*.DS_Store
# SwiftPM
Packages/
.build
Packages/
*.xcodeproj/
# CocoaPods
#
@@ -29,10 +27,10 @@ Packages/
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
#
Pods/
SlackKit.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
Carthage/Checkouts
# Carthage/Checkouts
Carthage/Build
+1 -1
View File
@@ -1 +1 @@
3.1.1
DEVELOPMENT-SNAPSHOT-2016-05-09-a
+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()
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017 Peter Zignego
Copyright (c) 2016 Peter Zignego
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+29 -8
View File
@@ -1,14 +1,35 @@
//
// Package.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 PackageDescription
let package = Package(
name: "SlackKit",
targets: [
Target(name: "SlackKit")
],
targets: [],
dependencies: [
.Package(url: "https://github.com/SlackKit/SKCore", "4.0.0"),
.Package(url: "https://github.com/SlackKit/SKClient", "4.0.0"),
.Package(url: "https://github.com/SlackKit/SKRTMAPI", "4.0.0"),
.Package(url: "https://github.com/SlackKit/SKServer", "4.0.0")
]
.Package(url: "https://github.com/Zewo/WebSocketClient", majorVersion: 0, minor: 2),
.Package(url: "https://github.com/open-swift/C7.git", majorVersion: 0, minor: 8),
.Package(url: "https://github.com/czechboy0/Jay.git", majorVersion: 0, minor: 6),
],
exclude: ["Examples"]
)
-28
View File
@@ -1,28 +0,0 @@
use_frameworks!
target 'SlackKit macOS' do
platform :osx, '10.11'
pod 'SKCore'
pod 'SKClient'
pod 'SKWebAPI'
pod 'SKRTMAPI'
pod 'SKServer'
end
target 'SlackKit iOS' do
platform :ios, '9.0'
pod 'SKCore'
pod 'SKClient'
pod 'SKWebAPI'
pod 'SKRTMAPI'
pod 'SKServer'
end
target 'SlackKit tvOS' do
platform :tvos, '9.0'
pod 'SKCore'
pod 'SKClient'
pod 'SKWebAPI'
pod 'SKRTMAPI'
pod 'SKServer'
end
+186 -162
View File
@@ -1,205 +1,229 @@
<p align="center"><img src="https://cloud.githubusercontent.com/assets/8311605/24083714/e921a0d4-0cb2-11e7-8384-d42113ef5056.png" alt="SlackKit" width="500"/></p>
![SlackKit](https://cloud.githubusercontent.com/assets/8311605/10260893/5ec60f96-694e-11e5-91fd-da6845942201.png)
##Alpha Linux Slack Client Library
###Description
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).
![Swift Version](https://img.shields.io/badge/Swift-3.1.1-orange.svg)
![Plaforms](https://img.shields.io/badge/Platforms-macOS,iOS,tvOS,Linux-lightgrey.svg)
![License MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg)
[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage)
[![CocoaPods compatible](https://img.shields.io/badge/CocoaPods-compatible-brightgreen.svg)](https://cocoapods.org)
###Disclaimer: The linux version of SlackKit is a pre-release alpha. Feel free to report issues you come across.
## SlackKit: Slack Apps in Swift
### Description
###Installation
SlackKit makes it easy to build Slack apps 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 to [bot users](https://api.slack.com/bot-users). SlackKit also supports Slacks [OAuth 2.0](https://api.slack.com/docs/oauth) flow including the [Add to Slack](https://api.slack.com/docs/slack-button) and [Sign in with Slack](https://api.slack.com/docs/sign-in-with-slack) buttons, [incoming webhooks](https://api.slack.com/incoming-webhooks), [slash commands](https://api.slack.com/slash-commands), and [message buttons](https://api.slack.com/docs/message-buttons).
### Installation
#### Swift Package Manager
Add `SlackKit` to your `Package.swift`
####Swift Package Manager
Add SlackKit to your Package.swift
```swift
import PackageDescription
let package = Package(
dependencies: [
.Package(url: "https://github.com/SlackKit/SlackKit.git", "4.0.0")
]
dependencies: [
.Package(url: "https://github.com/pvzig/SlackKit.git", majorVersion: 0, minor: 0)
]
)
```
#### Carthage
Add `SlackKit` to your `Cartfile`:
####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:
```
github "SlackKit/SlackKit"
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`
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:
```
swift build
swift build -Xlinker -L$(pwd)/.build/debug/ -Xswiftc -I/usr/local/include -Xlinker -L/usr/local/lib -X
```
#### CocoaPods
Add `SlackKit` to your `Podfile`:
To use the library in your project import it:
```
pod 'SlackKit'
```
### Usage
#### The Basics
Create a bot user with an API token:
```swift
import SlackKit
```
let bot = SlackKit()
bot.addRTMBotWithAPIToken("xoxb-SLACK-BOT-TOKEN")
// Register for event notifications
bot.notificationForEvent(.message) { (event, _) in
// Your bot logic here
print(event.message)
####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, initialize a client instance using it:
```swift
let client = Client(apiToken: "YOUR_SLACK_API_TOKEN")
```
If you want to receive messages from the Slack RTM API, connect to it.
```swift
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)
}
```
or create a ready-to-launch Slack app with your [applications `Client ID` and `Client Secret`](https://api.slack.com/apps):
####Delegate methods
To receive delegate callbacks for certain events, register an object as the delegate for those events:
```swift
import SlackKit
let bot = SlackKit()
let oauthConfig = OAuthConfig(clientID: "CLIENT_ID", clientSecret: "CLIENT_SECRET")
bot.addServer(oauth: oauthConfig)
client.slackEventsDelegate = self
```
or just make calls to the Slack Web API:
There are a number of delegates that you can set to receive callbacks for certain events.
#####SlackEventsDelegate
```swift
import SlackKit
let bot = SlackKit()
bot.addWebAPIAccessWithToken("xoxb-SLACK-BOT-TOKEN")
bot.webAPI?.authenticationTest(success: { (success) in
print(success)
}, failure: nil)
func clientConnected()
func clientDisconnected()
func preferenceChanged(preference: String, value: AnyObject)
func userChanged(user: User)
func presenceChanged(user: User?, presence: String?)
func manualPresenceChanged(user: User?, presence: String?)
func botEvent(bot: Bot)
```
#### Slash Commands
After [configuring your slash command in Slack](https://my.slack.com/services/new/slash-commands) (you can also provide slash commands as part of a [Slack App](https://api.slack.com/slack-apps)), create a route, response middleware for that route, and add it to a responder:
#####MessageEventsDelegate
```swift
let slackkit = SlackKit()
let middleware = ResponseMiddleware(token: "SLASH_COMMAND_TOKEN", response: SKResponse(text: "👋"))
let route = RequestRoute(path: "/hello", middleware: middleware)
let responder = SlackKitResponder(routes: [route])
slackkit.addServer(responder: responder)
```
When a user enters that slash command, it will hit your configured route and return the response you specified.
#### Message Buttons
Add [message buttons](https://api.slack.com/docs/message-buttons) to your responses for additional interactivity.
To send messages with actions, add them to an attachment and send them using the Web API:
```swift
let helloAction = Action(name: "hello", text: "🌎")
let attachment = Attachment(fallback: "Hello World", title: "Welcome to SlackKit", callbackID: "hello_world", actions: [helloAction])
slackkit.webAPI?.sendMessage(channel: "CXXXXXX", text: "", attachments: [attachment], success: nil, failure: nil)
func messageSent(message: Message)
func messageReceived(message: Message)
func messageChanged(message: Message)
func messageDeleted(message: Message?)
```
To respond to message actions, add a `RequestRoute` with `MessageActionMiddleware` using your apps verification token to your `SlackKitResponder`:
#####ChannelEventsDelegate
```swift
let response = ResponseMiddleware(token: "SLACK_APP_VERIFICATION_TOKEN", response: SKResponse(text: "Hello, world!"))
let actionRoute = MessageActionRoute(action: helloAction, middleware: response)
let actionMiddleware = MessageActionMiddleware(token: "SLACK_APP_VERIFICATION_TOKEN", routes:[actionRoute])
let actions = RequestRoute(path: "/actions", middleware: actionMiddleware)
let responder = SlackKitResponder(routes: [actions])
slackkit.addServer(responder: responder)
func userTyping(channel: Channel?, user: User?)
func channelMarked(channel: Channel, timestamp: String?)
func channelCreated(channel: Channel)
func channelDeleted(channel: Channel)
func channelRenamed(channel: Channel)
func channelArchived(channel: Channel)
func channelHistoryChanged(channel: Channel)
func channelJoined(channel: Channel)
func channelLeft(channel: Channel)
```
#### OAuth
Slack has [many different oauth scopes](https://api.slack.com/docs/oauth-scopes) that can be combined in different ways. If your application does not request the proper OAuth scopes, your API calls will fail.
#####DoNotDisturbEventsDelegate
```swift
doNotDisturbUpdated(dndStatus: DoNotDisturbStatus)
doNotDisturbUserUpdated(dndStatus: DoNotDisturbStatus, user: User?)
```
If you authenticate using OAuth and the Add to Slack or Sign in with Slack buttons this is handled for you.
#####GroupEventsDelegate
```swift
func groupOpened(group: Channel)
```
For local development of things like OAuth, slash commands, and message buttons, you may want to use a tool like [ngrok](https://ngrok.com).
#####FileEventsDelegate
```swift
func fileProcessed(file: File)
func fileMadePrivate(file: File)
func fileDeleted(file: File)
func fileCommentAdded(file: File, comment: Comment)
func fileCommentEdited(file: File, comment: Comment)
func fileCommentDeleted(file: File, comment: Comment)
```
#### Web API Methods
SlackKit currently supports the a subset of the Slack Web APIs that are available to bot users:
#####PinEventsDelegate
```swift
func itemPinned(item: Item?, channel: Channel?)
func itemUnpinned(item: Item?, channel: Channel?)
```
| Web APIs |
| ------------- |
| `api.test`|
| `api.revoke`|
| `auth.test`|
| `channels.history`|
| `channels.info`|
| `channels.list`|
| `channels.mark`|
| `channels.setPurpose`|
| `channels.setTopic`|
| `chat.delete`|
| `chat.meMessage`|
| `chat.postMessage`|
| `chat.update`|
| `emoji.list`|
| `files.comments.add`|
| `files.comments.edit`|
| `files.comments.delete`|
| `files.delete`|
| `files.info`|
| `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`|
| `oauth.access`|
| `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`|
#####StarEventsDelegate
```swift
func itemStarred(item: Item, star: Bool)
```
Dont need the whole banana? Want more control over the low-level implementation details? Use the extensible modules SlackKit is built on:
#####ReactionEventsDelegate
```swift
func reactionAdded(reaction: String?, item: Item?, itemUser: String?)
func reactionRemoved(reaction: String?, item: Item?, itemUser: String?)
```
| Module | Slack Service |
| ------------- |------------- |
| **[SKClient](https://github.com/SlackKit/SKClient)** | Write your own client implementation|
| **[SKRTMAPI](https://github.com/SlackKit/SKRTMAPI)** | Connect to the Slack RTM API|
| **[SKServer](https://github.com/SlackKit/SKServer)** | Spin up a server|
| **[SKWebAPI](https://github.com/SlackKit/SKWebAPI)** | Access the Slack Web API|
#####TeamEventsDelegate
```swift
func teamJoined(user: User)
func teamPlanChanged(plan: String)
func teamPreferencesChanged(preference: String, value: AnyObject)
func teamNameChanged(name: String)
func teamDomainChanged(domain: String)
func teamEmailDomainChanged(domain: String)
func teamEmojiChanged()
```
### Examples
You can find the source code for several example applications [here](https://github.com/SlackKit/Examples).
### Tutorials
Coming soon!
#####SubteamEventsDelegate
```swift
func subteamEvent(userGroup: UserGroup)
func subteamSelfAdded(subteamID: String)
func subteamSelfRemoved(subteamID: String)
```
### Get In Touch
Twitter: [@pvzig](https://twitter.com/pvzig)
###Get In Touch
[@pvzig](https://twitter.com/pvzig)
Email: <peter@launchsoft.co>
<peter@launchsoft.co>
-20
View File
@@ -1,20 +0,0 @@
Pod::Spec.new do |s|
s.name = "SlackKit"
s.version = "4.0.0"
s.summary = "Write Slack apps 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/SlackKit/SlackKit.git", :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/pvzig'
s.ios.deployment_target = '9.0'
s.osx.deployment_target = '10.11'
s.tvos.deployment_target = '9.0'
s.requires_arc = true
s.source_files = 'Sources/*.swift'
s.frameworks = 'Foundation'
s.dependency 'SKCore'
s.dependency 'SKClient'
s.dependency 'SKRTMAPI'
s.dependency 'SKServer'
end
-543
View File
@@ -1,543 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
266687051E95CB9F00777D94 /* SlackKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 266687041E95CB9F00777D94 /* SlackKit.swift */; };
266687061E95CB9F00777D94 /* SlackKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 266687041E95CB9F00777D94 /* SlackKit.swift */; };
266687071E95CB9F00777D94 /* SlackKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 266687041E95CB9F00777D94 /* SlackKit.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
266687041E95CB9F00777D94 /* SlackKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlackKit.swift; sourceTree = "<group>"; };
2684F17D1E95AA6900536DCC /* SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2684F1E41E95ABD400536DCC /* SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2684F2081E95ABD600536DCC /* SlackKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SlackKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2684F20D1E95AF8C00536DCC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
2684F1791E95AA6900536DCC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2684F1DE1E95ABD400536DCC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2684F2021E95ABD600536DCC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
266687031E95CB9F00777D94 /* Sources */ = {
isa = PBXGroup;
children = (
266687041E95CB9F00777D94 /* SlackKit.swift */,
);
path = Sources;
sourceTree = SOURCE_ROOT;
};
2684F1731E95AA6900536DCC = {
isa = PBXGroup;
children = (
2684F17F1E95AA6900536DCC /* SlackKit */,
2684F17E1E95AA6900536DCC /* Products */,
);
sourceTree = "<group>";
};
2684F17E1E95AA6900536DCC /* Products */ = {
isa = PBXGroup;
children = (
2684F17D1E95AA6900536DCC /* SlackKit.framework */,
2684F1E41E95ABD400536DCC /* SlackKit.framework */,
2684F2081E95ABD600536DCC /* SlackKit.framework */,
);
name = Products;
sourceTree = "<group>";
};
2684F17F1E95AA6900536DCC /* SlackKit */ = {
isa = PBXGroup;
children = (
266687031E95CB9F00777D94 /* Sources */,
2684F20C1E95AF8C00536DCC /* Supporting Files */,
);
name = SlackKit;
path = SKCore;
sourceTree = "<group>";
};
2684F20C1E95AF8C00536DCC /* Supporting Files */ = {
isa = PBXGroup;
children = (
2684F20D1E95AF8C00536DCC /* Info.plist */,
);
path = "Supporting Files";
sourceTree = SOURCE_ROOT;
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
2684F17A1E95AA6900536DCC /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2684F1DF1E95ABD400536DCC /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2684F2031E95ABD600536DCC /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
2684F17C1E95AA6900536DCC /* SlackKit macOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2684F1851E95AA6900536DCC /* Build configuration list for PBXNativeTarget "SlackKit macOS" */;
buildPhases = (
2684F1781E95AA6900536DCC /* Sources */,
2684F1791E95AA6900536DCC /* Frameworks */,
2684F17A1E95AA6900536DCC /* Headers */,
2684F17B1E95AA6900536DCC /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "SlackKit macOS";
productName = SKCore;
productReference = 2684F17D1E95AA6900536DCC /* SlackKit.framework */;
productType = "com.apple.product-type.framework";
};
2684F1C21E95ABD400536DCC /* SlackKit iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2684F1E11E95ABD400536DCC /* Build configuration list for PBXNativeTarget "SlackKit iOS" */;
buildPhases = (
2684F1C31E95ABD400536DCC /* Sources */,
2684F1DE1E95ABD400536DCC /* Frameworks */,
2684F1DF1E95ABD400536DCC /* Headers */,
2684F1E01E95ABD400536DCC /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "SlackKit iOS";
productName = SKCore;
productReference = 2684F1E41E95ABD400536DCC /* SlackKit.framework */;
productType = "com.apple.product-type.framework";
};
2684F1E61E95ABD600536DCC /* SlackKit tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2684F2051E95ABD600536DCC /* Build configuration list for PBXNativeTarget "SlackKit tvOS" */;
buildPhases = (
2684F1E71E95ABD600536DCC /* Sources */,
2684F2021E95ABD600536DCC /* Frameworks */,
2684F2031E95ABD600536DCC /* Headers */,
2684F2041E95ABD600536DCC /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "SlackKit tvOS";
productName = SKCore;
productReference = 2684F2081E95ABD600536DCC /* SlackKit.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
2684F1741E95AA6900536DCC /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = "Peter Zignego";
TargetAttributes = {
2684F17C1E95AA6900536DCC = {
CreatedOnToolsVersion = 8.3;
ProvisioningStyle = Manual;
};
2684F1C21E95ABD400536DCC = {
DevelopmentTeam = U63DWZL52M;
};
2684F1E61E95ABD600536DCC = {
DevelopmentTeam = U63DWZL52M;
};
};
};
buildConfigurationList = 2684F1771E95AA6900536DCC /* Build configuration list for PBXProject "SlackKit" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 2684F1731E95AA6900536DCC;
productRefGroup = 2684F17E1E95AA6900536DCC /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
2684F17C1E95AA6900536DCC /* SlackKit macOS */,
2684F1C21E95ABD400536DCC /* SlackKit iOS */,
2684F1E61E95ABD600536DCC /* SlackKit tvOS */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
2684F17B1E95AA6900536DCC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2684F1E01E95ABD400536DCC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2684F2041E95ABD600536DCC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
2684F1781E95AA6900536DCC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
266687051E95CB9F00777D94 /* SlackKit.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2684F1C31E95ABD400536DCC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
266687061E95CB9F00777D94 /* SlackKit.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2684F1E71E95ABD600536DCC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
266687071E95CB9F00777D94 /* SlackKit.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
2684F1831E95AA6900536DCC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
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_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
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.12;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
2684F1841E95AA6900536DCC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
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_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
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.12;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
2684F1861E95AA6900536DCC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit;
PRODUCT_NAME = SlackKit;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
2684F1871E95AA6900536DCC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit;
PRODUCT_NAME = SlackKit;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
2684F1E21E95ABD400536DCC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = U63DWZL52M;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit;
PRODUCT_NAME = SlackKit;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
2684F1E31E95ABD400536DCC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = U63DWZL52M;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.launchsoft.SlackKit;
PRODUCT_NAME = SlackKit;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
2684F2061E95ABD600536DCC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = U63DWZL52M;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/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;
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
TVOS_DEPLOYMENT_TARGET = 9.0;
};
name = Debug;
};
2684F2071E95ABD600536DCC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = U63DWZL52M;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/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;
SDKROOT = appletvos;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
TVOS_DEPLOYMENT_TARGET = 9.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
2684F1771E95AA6900536DCC /* Build configuration list for PBXProject "SlackKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2684F1831E95AA6900536DCC /* Debug */,
2684F1841E95AA6900536DCC /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2684F1851E95AA6900536DCC /* Build configuration list for PBXNativeTarget "SlackKit macOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2684F1861E95AA6900536DCC /* Debug */,
2684F1871E95AA6900536DCC /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2684F1E11E95ABD400536DCC /* Build configuration list for PBXNativeTarget "SlackKit iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2684F1E21E95ABD400536DCC /* Debug */,
2684F1E31E95ABD400536DCC /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2684F2051E95ABD600536DCC /* Build configuration list for PBXNativeTarget "SlackKit tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2684F2061E95ABD600536DCC /* Debug */,
2684F2071E95ABD600536DCC /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 2684F1741E95AA6900536DCC /* Project object */;
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:SKCore.xcodeproj">
</FileRef>
</Workspace>
@@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2684F1C21E95ABD400536DCC"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit iOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2684F1C21E95ABD400536DCC"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit iOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2684F1C21E95ABD400536DCC"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit iOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2684F17C1E95AA6900536DCC"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit macOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2684F17C1E95AA6900536DCC"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit macOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2684F17C1E95AA6900536DCC"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit macOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2684F1E61E95ABD600536DCC"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit tvOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2684F1E61E95ABD600536DCC"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit tvOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2684F1E61E95ABD600536DCC"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit tvOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -15,11 +15,13 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>4.0.0</string>
<string>1.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2017 Peter Zignego. All rights reserved.</string>
<string>Copyright © 2016 Peter Zignego. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
+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"
}
+36
View File
@@ -0,0 +1,36 @@
//
// Bot.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 Bot {
public let id: String?
internal(set) public var name: String?
internal(set) public var icons: [String: Any]?
internal init?(bot: [String: Any]?) {
id = bot?["id"] as? String
name = bot?["name"] as? String
icons = bot?["icons"] as? [String: Any]
}
}
+89
View File
@@ -0,0 +1,89 @@
//
// Channel.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 Channel {
public let id: String?
public let created: Int?
public let creator: String?
internal(set) public var name: String?
internal(set) public var isArchived: Bool?
internal(set) public var isGeneral: Bool?
public let isGroup: Bool?
public let isIM: Bool?
public let isMPIM: Bool?
internal(set) public var user: String?
internal(set) public var isUserDeleted: Bool?
internal(set) public var isOpen: Bool?
internal(set) public var topic: Topic?
internal(set) public var purpose: Topic?
internal(set) public var isMember: Bool?
internal(set) public var lastRead: String?
internal(set) public var latest: Message?
internal(set) public var unread: Int?
internal(set) public var unreadCountDisplay: Int?
internal(set) public var hasPins: Bool?
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: Any]?) {
id = channel?["id"] as? String
name = channel?["name"] as? String
created = channel?["created"] as? Int
creator = channel?["creator"] as? String
isArchived = channel?["is_archived"] as? Bool
isGeneral = channel?["is_general"] as? Bool
isGroup = channel?["is_group"] as? Bool
isIM = channel?["is_im"] as? Bool
isMPIM = channel?["is_mpim"] as? Bool
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: Any])
purpose = Topic(topic: channel?["purpose"] as? [String: Any])
isMember = channel?["is_member"] as? Bool
lastRead = channel?["last_read"] as? String
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 (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?) {
self.id = id
created = nil
creator = nil
isGroup = false
isIM = false
isMPIM = false
}
}
@@ -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)
}
}
+53
View File
@@ -0,0 +1,53 @@
//
// Client+Utilities.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.
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
}
}
+289
View File
@@ -0,0 +1,289 @@
//
// Client.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
import Venice
import WebSocketClient
public class SlackClient {
internal(set) public var connected = false
internal(set) public var authenticated = false
internal(set) public var authenticatedUser: User?
internal(set) public var team: Team?
internal(set) public var channels = [String: Channel]()
internal(set) public var users = [String: User]()
internal(set) public var userGroups = [String: UserGroup]()
internal(set) public var bots = [String: Bot]()
internal(set) public var files = [String: File]()
internal(set) public var sentMessages = [String: Message]()
//MARK: - Delegates
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
}
public var webAPI: SlackWebAPI {
return SlackWebAPI(slackClient: self)
}
internal var webSocket: Client?
internal var socket: WebSocket?
internal let api = NetworkInterface()
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 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 Client(uri: uri, didConnect: {(socket) in
self.setupSocket(socket: socket)
if let pingInterval = self.pingInterval {
self.pingRTMServerAtInterval(interval: pingInterval)
}
})
try self.webSocket?.connect(uri.description)
} catch _ {
}
}
}, failure:nil)
}
/*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(message: message, channel: channelID) {
if let string = try? data.string() {
_ = try? socket?.send(string)
}
}
}
}
private func formatMessageToSlackJsonString(message: String, channel: String) -> Data? {
let json: [String: Any] = [
"id": Time.slackTimestamp(),
"type": "message",
"channel": channel,
"text": message.slackFormatEscaping()
]
do {
let bytes = try Jay().dataFromJson(json)
return Data(bytes)
} catch {
return nil
}
}
private func addSentMessage(dictionary: [String: Any]) {
var message = dictionary
let ts = message["id"] as? Int
message.removeValue(forKey:"id")
message["ts"] = "\(ts)"
message["user"] = self.authenticatedUser?.id
sentMessages["\(ts)"] = Message(message: message)
}
//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: 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 addUser(aUser: [String: Any]) {
if let user = User(user: aUser), id = user.id {
users[id] = user
}
}
private func addChannel(aChannel: [String: Any]) {
if let channel = Channel(channel: aChannel), id = channel.id {
channels[id] = channel
}
}
private func addBot(aBot: [String: Any]) {
if let bot = Bot(bot: aBot), id = bot.id {
bots[id] = bot
}
}
private func enumerateSubteams(subteams: [String: Any]?) {
if let subteams = subteams {
if let all = subteams["all"] as? [Any] {
for item in all {
let u = UserGroup(userGroup: item as? [String: Any])
self.userGroups[u!.id!] = u
}
}
if let auth = subteams["self"] as? [String] {
for item in auth {
authenticatedUser?.userGroups = [String: String]()
authenticatedUser?.userGroups![item] = item
}
}
}
}
// 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)
}
}
}
}
// MARK: - WebSocket
private func setupSocket(socket: WebSocket) {
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 {
let json = try Jay().jsonFromData(message.data.bytes)
if let event = json as? [String: Any] {
dispatch(event:event)
}
}
catch _ {
}
}
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)
}
}
}
+225
View File
@@ -0,0 +1,225 @@
//
// Message.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 enum EventType: String {
case Hello = "hello"
case Message = "message"
case UserTyping = "user_typing"
case ChannelMarked = "channel_marked"
case ChannelCreated = "channel_created"
case ChannelJoined = "channel_joined"
case ChannelLeft = "channel_left"
case ChannelDeleted = "channel_deleted"
case ChannelRenamed = "channel_rename"
case ChannelArchive = "channel_archive"
case ChannelUnarchive = "channel_unarchive"
case ChannelHistoryChanged = "channel_history_changed"
case DNDUpdated = "dnd_updated"
case DNDUpatedUser = "dnd_updated_user"
case IMCreated = "im_created"
case IMOpen = "im_open"
case IMClose = "im_close"
case IMMarked = "im_marked"
case IMHistoryChanged = "im_history_changed"
case GroupJoined = "group_joined"
case GroupLeft = "group_left"
case GroupOpen = "group_open"
case GroupClose = "group_close"
case GroupArchive = "group_archive"
case GroupUnarchive = "group_unarchive"
case GroupRename = "group_rename"
case GroupMarked = "group_marked"
case GroupHistoryChanged = "group_history_changed"
case FileCreated = "file_created"
case FileShared = "file_shared"
case FileUnshared = "file_unshared"
case FilePublic = "file_public"
case FilePrivate = "file_private"
case FileChanged = "file_change"
case FileDeleted = "file_deleted"
case FileCommentAdded = "file_comment_added"
case FileCommentEdited = "file_comment_edited"
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"
case UserChange = "user_change"
case TeamJoin = "team_join"
case StarAdded = "star_added"
case StarRemoved = "star_removed"
case ReactionAdded = "reaction_added"
case ReactionRemoved = "reaction_removed"
case EmojiChanged = "emoji_changed"
case CommandsChanged = "commands_changed"
case TeamPlanChange = "team_plan_change"
case TeamPrefChange = "team_pref_change"
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"
case SubteamSelfRemoved = "subteam_self_removed"
case Ok = "ok"
case Error = "error"
}
internal enum MessageSubtype: String {
case BotMessage = "bot_message"
case MeMessage = "me_message"
case MessageChanged = "message_changed"
case MessageDeleted = "message_deleted"
case ChannelJoin = "channel_join"
case ChannelLeave = "channel_leave"
case ChannelTopic = "channel_topic"
case ChannelPurpose = "channel_purpose"
case ChannelName = "channel_name"
case ChannelArchive = "channel_archive"
case ChannelUnarchive = "channel_unarchive"
case GroupJoin = "group_join"
case GroupLeave = "group_leave"
case GroupTopic = "group_topic"
case GroupPurpose = "group_purpose"
case GroupName = "group_name"
case GroupArchive = "group_archive"
case GroupUnarchive = "group_unarchive"
case FileShare = "file_share"
case FileComment = "file_comment"
case FileMention = "file_mention"
case PinnedItem = "pinned_item"
case UnpinnedItem = "unpinned_item"
}
internal struct Event {
let type: EventType?
let ts: String?
let subtype: String?
let channelID: String?
let text: String?
let eventTs: String?
let latest: String?
let hidden: Bool?
let isStarred: Bool?
let hasPins: Bool?
let pinnedTo: [String]?
let fileID: String?
let presence: String?
let name: String?
let value: Any?
let plan: String?
let url: String?
let domain: String?
let emailDomain: String?
let reaction: String?
let replyTo: Double?
let edited: Edited?
let bot: Bot?
let channel: Channel?
let comment: Comment?
let user: User?
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: Any]) {
if let eventType = event["type"] as? String {
type = EventType(rawValue:eventType)
} else {
type = EventType(rawValue: "ok")
}
ts = event["ts"] as? String
subtype = event["subtype"] as? String
channelID = event["channel_id"] as? String
text = event["text"] as? String
eventTs = event["event_ts"] as? String
latest = event["latest"] as? String
hidden = event["hidden"] as? Bool
isStarred = event["is_starred"] as? Bool
hasPins = event["has_pins"] as? Bool
pinnedTo = event["pinned_top"] as? [String]
fileID = event["file_id"] as? String
presence = event["presence"] as? String
name = event["name"] as? String
value = event["value"]
plan = event["plan"] as? String
url = event["url"] as? String
domain = event["domain"] as? String
emailDomain = event["email_domain"] as? String
reaction = event["reaction"] as? String
replyTo = event["reply_to"] as? Double
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: 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: Any])?.id == nil) {
comment = Comment(id: event["comment"] as? String)
} else {
comment = Comment(comment: event["comment"] as? [String: Any])
}
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: Any])
}
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: Any])
}
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: Any])
}
}
}
+105
View File
@@ -0,0 +1,105 @@
//
// EventDelegate.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 protocol SlackEventsDelegate: class {
func clientConnected()
func clientDisconnected()
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: class {
func messageSent(message: Message)
func messageReceived(message: Message)
func messageChanged(message: Message)
func messageDeleted(message: Message?)
}
public protocol ChannelEventsDelegate: class {
func userTyping(channel: Channel?, user: User?)
func channelMarked(channel: Channel, timestamp: String?)
func channelCreated(channel: Channel)
func channelDeleted(channel: Channel)
func channelRenamed(channel: Channel)
func channelArchived(channel: Channel)
func channelHistoryChanged(channel: Channel)
func channelJoined(channel: Channel)
func channelLeft(channel: Channel)
}
public protocol DoNotDisturbEventsDelegate: class {
func doNotDisturbUpdated(dndStatus: DoNotDisturbStatus)
func doNotDisturbUserUpdated(dndStatus: DoNotDisturbStatus, user: User?)
}
public protocol GroupEventsDelegate: class {
func groupOpened(group: Channel)
}
public protocol FileEventsDelegate: class {
func fileProcessed(file: File)
func fileMadePrivate(file: File)
func fileDeleted(file: File)
func fileCommentAdded(file: File, comment: Comment)
func fileCommentEdited(file: File, comment: Comment)
func fileCommentDeleted(file: File, comment: Comment)
}
public protocol PinEventsDelegate: class {
func itemPinned(item: Item?, channel: Channel?)
func itemUnpinned(item: Item?, channel: Channel?)
}
public protocol StarEventsDelegate: class {
func itemStarred(item: Item, star: Bool)
}
public protocol ReactionEventsDelegate: class {
func reactionAdded(reaction: String?, item: Item?, itemUser: String?)
func reactionRemoved(reaction: String?, item: Item?, itemUser: String?)
}
public protocol TeamEventsDelegate: class {
func teamJoined(user: User)
func teamPlanChanged(plan: String)
func teamPreferencesChanged(preference: String, value: Any)
func teamNameChanged(name: String)
func teamDomainChanged(domain: String)
func teamEmailDomainChanged(domain: String)
func teamEmojiChanged()
}
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?)
}
+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
}
}
+144
View File
@@ -0,0 +1,144 @@
//
// File.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 File {
public let id: String?
public let created: Int?
public let name: String?
public let title: String?
public let mimeType: String?
public let fileType: String?
public let prettyType: String?
public let user: String?
public let mode: String?
internal(set) public var editable: Bool?
public let isExternal: Bool?
public let externalType: String?
public let size: Int?
public let urlPrivate: String?
public let urlPrivateDownload: String?
public let thumb64: String?
public let thumb80: String?
public let thumb360: String?
public let thumb360gif: String?
public let thumb360w: String?
public let thumb360h: String?
public let permalink: String?
public let editLink: String?
public let preview: String?
public let previewHighlight: String?
public let lines: Int?
public let linesMore: Int?
internal(set) public var isPublic: Bool?
internal(set) public var publicSharedURL: Bool?
internal(set) public var channels: [String]?
internal(set) public var groups: [String]?
internal(set) public var ims: [String]?
public let initialComment: Comment?
internal(set) public var stars: Int?
internal(set) public var isStarred: Bool?
internal(set) public var pinnedTo: [String]?
internal(set) public var comments = [String: Comment]()
internal(set) public var reactions = [String: Reaction]()
public init?(file:[String: Any]?) {
id = file?["id"] as? String
created = file?["created"] as? Int
name = file?["name"] as? String
title = file?["title"] as? String
mimeType = file?["mimetype"] as? String
fileType = file?["filetype"] as? String
prettyType = file?["pretty_type"] as? String
user = file?["user"] as? String
mode = file?["mode"] as? String
editable = file?["editable"] as? Bool
isExternal = file?["is_external"] as? Bool
externalType = file?["external_type"] as? String
size = file?["size"] as? Int
urlPrivate = file?["url_private"] as? String
urlPrivateDownload = file?["url_private_download"] as? String
thumb64 = file?["thumb_64"] as? String
thumb80 = file?["thumb_80"] as? String
thumb360 = file?["thumb_360"] as? String
thumb360gif = file?["thumb_360_gif"] as? String
thumb360w = file?["thumb_360_w"] as? String
thumb360h = file?["thumb_360_h"] as? String
permalink = file?["permalink"] as? String
editLink = file?["edit_link"] as? String
preview = file?["preview"] as? String
previewHighlight = file?["preview_highlight"] as? String
lines = file?["lines"] as? Int
linesMore = file?["lines_more"] as? Int
isPublic = file?["is_public"] as? Bool
publicSharedURL = file?["public_url_shared"] as? Bool
channels = file?["channels"] as? [String]
groups = file?["groups"] as? [String]
ims = file?["ims"] as? [String]
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? [Any] {
self.reactions = Reaction.reactionsFromArray(array: reactions)
}
}
internal init?(id:String?) {
self.id = id
created = nil
name = nil
title = nil
mimeType = nil
fileType = nil
prettyType = nil
user = nil
mode = nil
isExternal = nil
externalType = nil
size = nil
urlPrivate = nil
urlPrivateDownload = nil
thumb64 = nil
thumb80 = nil
thumb360 = nil
thumb360gif = nil
thumb360w = nil
thumb360h = nil
permalink = nil
editLink = nil
preview = nil
previewHighlight = nil
lines = nil
linesMore = nil
initialComment = nil
}
}
extension File: Equatable {}
public func ==(lhs: File, rhs: File) -> Bool {
return lhs.id == rhs.id
}
+111
View File
@@ -0,0 +1,111 @@
//
// Message.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 class Message {
public let type = "message"
public let subtype: String?
internal(set) public var ts: String?
public let user: String?
public let channel: String?
internal(set) public var hidden: Bool?
internal(set) public var text: String?
public let botID: String?
public let username: String?
public let icons: [String: Any]?
public let deletedTs: String?
internal(set) var purpose: String?
internal(set) var topic: String?
internal(set) var name: String?
internal(set) var members: [String]?
internal(set) var oldName: String?
public let upload: Bool?
public let itemType: String?
internal(set) public var isStarred: Bool?
internal(set) var pinnedTo: [String]?
public let comment: Comment?
public let file: File?
internal(set) public var reactions = [String: Reaction]()
internal(set) public var attachments: [Attachment]?
public init?(message: [String: Any]?) {
subtype = message?["subtype"] as? String
ts = message?["ts"] as? String
user = message?["user"] as? String
channel = message?["channel"] as? String
hidden = message?["hidden"] as? Bool
text = message?["text"] as? String
botID = message?["bot_id"] as? String
username = message?["username"] as? String
icons = message?["icons"] as? [String: Any]
deletedTs = message?["deleted_ts"] as? String
purpose = message?["purpose"] as? String
topic = message?["topic"] as? String
name = message?["name"] as? String
members = message?["members"] as? [String]
oldName = message?["old_name"] as? String
upload = message?["upload"] as? Bool
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: 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
}
}
extension Message: Equatable {}
public func ==(lhs: Message, rhs: Message) -> Bool {
return lhs.ts == rhs.ts && lhs.user == rhs.user && lhs.text == rhs.text
}
+155
View File
@@ -0,0 +1,155 @@
//
// 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
import WebSocketClient
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 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":"multipart/form-data; boundary=" + boundaryConstant]
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 {
#if os(Linux)
return "slackkit.boundary.\(Int(random()))\(Int(random()))"
#else
return "slackkit.boundary.\(arc4random())\(arc4random())"
#endif
}
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
}
}
}
+70
View File
@@ -0,0 +1,70 @@
//
// Team.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 Team {
public let id: String
internal(set) public var name: 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: Any]?
internal(set) public var plan: String?
internal(set) public var icon: TeamIcon?
internal init?(team: [String: Any]?) {
id = team?["id"] as! String
name = team?["name"] as? String
domain = team?["domain"] as? String
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: 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
}
}
+274
View File
@@ -0,0 +1,274 @@
//
// Types.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.
// MARK: - Edited
public struct Edited {
public let user: String?
public let ts: String?
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: Any]?) {
name = reaction?["name"] as? String
}
internal init?(name: String?, user: String) {
self.name = name
users[user] = user
}
internal init?(name: String?, users: [String: String]) {
self.name = name
self.users = users
}
static func reactionsFromArray(array: [Any]) -> [String: Reaction] {
var reactions = [String: Reaction]()
var userDictionary = [String: String]()
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)
}
}
}
return reactions
}
}
extension Reaction: Equatable {}
public func ==(lhs: Reaction, rhs: Reaction) -> Bool {
return lhs.name == rhs.name
}
// MARK: - Comment
public struct Comment {
public let id: String?
public let user: String?
internal(set) public var created: Int?
internal(set) public var comment: String?
internal(set) public var starred: Bool?
internal(set) public var stars: Int?
internal(set) public var reactions = [String: Reaction]()
internal init?(comment:[String: Any]?) {
id = comment?["id"] as? String
created = comment?["created"] as? Int
user = comment?["user"] as? String
starred = comment?["is_starred"] as? Bool
stars = comment?["num_stars"] as? Int
self.comment = comment?["comment"] as? String
}
internal init?(id: String?) {
self.id = id
self.user = nil
}
}
extension Comment: Equatable {}
public func ==(lhs: Comment, rhs: Comment) -> Bool {
return lhs.id == rhs.id
}
// MARK: - Item
public struct Item {
public let type: String?
public let ts: String?
public let channel: String?
public let message: Message?
public let file: File?
public let comment: Comment?
public let fileCommentID: String?
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: Any])
// Comment and File can come across as Strings or Dictionaries
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: Any])
}
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: Any])
}
fileCommentID = item?["file_comment"] as? String
}
}
extension Item: Equatable {}
public func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.type == rhs.type && lhs.channel == rhs.channel && lhs.file == rhs.file && lhs.comment == rhs.comment && lhs.message == rhs.message
}
// MARK: - Topic
public struct Topic {
public let value: String?
public let creator: String?
public let lastSet: Int?
internal init?(topic: [String: Any]?) {
value = topic?["value"] as? String
creator = topic?["creator"] as? String
lastSet = topic?["last_set"] as? Int
}
}
// MARK: - Do Not Disturb Status
public struct DoNotDisturbStatus {
internal(set) public var enabled: Bool?
internal(set) public var nextDoNotDisturbStart: Int?
internal(set) public var nextDoNotDisturbEnd: Int?
internal(set) public var snoozeEnabled: Bool?
internal(set) public var snoozeEndtime: Int?
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
snoozeEnabled = status?["snooze_enabled"] as? Bool
snoozeEndtime = status?["snooze_endtime"] as? Int
}
}
// 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
}
}
+106
View File
@@ -0,0 +1,106 @@
//
// User.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 User {
public struct Profile {
internal(set) public var firstName: String?
internal(set) public var lastName: String?
internal(set) public var realName: String?
internal(set) public var email: String?
internal(set) public var skype: String?
internal(set) public var phone: String?
internal(set) public var image24: String?
internal(set) public var image32: String?
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: Any]?) {
firstName = profile?["first_name"] as? String
lastName = profile?["last_name"] as? String
realName = profile?["real_name"] as? String
email = profile?["email"] as? String
skype = profile?["skype"] as? String
phone = profile?["phone"] as? String
image24 = profile?["image_24"] as? String
image32 = profile?["image_32"] as? String
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?
internal(set) public var profile: Profile?
internal(set) public var doNotDisturbStatus: DoNotDisturbStatus?
internal(set) public var presence: String?
internal(set) public var color: String?
public let isBot: Bool?
internal(set) public var isAdmin: Bool?
internal(set) public var isOwner: Bool?
internal(set) public var isPrimaryOwner: Bool?
internal(set) public var isRestricted: Bool?
internal(set) public var isUltraRestricted: Bool?
internal(set) public var has2fa: Bool?
internal(set) public var hasFiles: Bool?
internal(set) public var status: String?
internal(set) public var timeZone: String?
internal(set) public var timeZoneLabel: String?
internal(set) public var timeZoneOffSet: Int?
internal(set) public var preferences: [String: Any]?
// Client properties
internal(set) public var userGroups: [String: String]?
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: Any])
color = user?["color"] as? String
isAdmin = user?["is_admin"] as? Bool
isOwner = user?["is_owner"] as? Bool
isPrimaryOwner = user?["is_primary_owner"] as? Bool
isRestricted = user?["is_restricted"] as? Bool
isUltraRestricted = user?["is_ultra_restricted"] as? Bool
has2fa = user?["has_2fa"] as? Bool
hasFiles = user?["has_files"] as? Bool
isBot = user?["is_bot"] as? Bool
presence = user?["presence"] as? String
status = user?["status"] as? String
timeZone = user?["tz"] as? String
timeZoneLabel = user?["tz_label"] as? String
timeZoneOffSet = user?["tz_offset"] as? Int
preferences = user?["prefs"] as? [String: Any]
}
internal init?(id: String?) {
self.id = id
self.isBot = nil
}
}
+68
View File
@@ -0,0 +1,68 @@
//
// UserGroup.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 UserGroup {
public let id: String?
internal(set) public var teamID: String?
public let isUserGroup: Bool?
internal(set) public var name: String?
internal(set) public var description: String?
internal(set) public var handle: String?
internal(set) public var isExternal: Bool?
public let dateCreated: Int?
internal(set) public var dateUpdated: Int?
internal(set) public var dateDeleted: Int?
internal(set) public var autoType: String?
public let createdBy: String?
internal(set) public var updatedBy: String?
internal(set) public var deletedBy: String?
internal(set) public var preferences: [String: Any]?
internal(set) public var users: [String]?
internal(set) public var userCount: Int?
internal init?(userGroup: [String: Any]?) {
id = userGroup?["id"] as? String
teamID = userGroup?["team_id"] as? String
isUserGroup = userGroup?["is_usergroup"] as? Bool
name = userGroup?["name"] as? String
description = userGroup?["description"] as? String
handle = userGroup?["handle"] as? String
isExternal = userGroup?["is_external"] as? Bool
dateCreated = userGroup?["date_create"] as? Int
dateUpdated = userGroup?["date_update"] as? Int
dateDeleted = userGroup?["date_delete"] as? Int
autoType = userGroup?["auto_type"] as? String
createdBy = userGroup?["created_by"] as? String
updatedBy = userGroup?["updated_by"] as? String
deletedBy = userGroup?["deleted_by"] as? String
preferences = userGroup?["prefs"] as? [String: Any]
users = userGroup?["users"] as? [String]
if let count = userGroup?["user_count"] as? String {
userCount = Int(count)
}
}
}
-104
View File
@@ -1,104 +0,0 @@
//
// SlackKit.swift
//
// Copyright © 2017 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
@_exported import SKClient
@_exported import SKCore
@_exported import SKRTMAPI
@_exported import SKServer
@_exported import SKWebAPI
public final class SlackKit: RTMAdapter {
public typealias EventClosure = (Event, Client?) -> Void
internal typealias TypedEvent = (EventType, EventClosure)
internal var callbacks = [TypedEvent]()
internal(set) public var rtm: SKRTMAPI?
internal(set) public var server: SKServer?
internal(set) public var webAPI: WebAPI?
internal(set) public var clients: [String: Client] = [:]
public init() {}
public func addWebAPIAccessWithToken(_ token: String) {
self.webAPI = WebAPI(token: token)
}
public func addRTMBotWithAPIToken(_ token: String, client: Client? = Client(), options: RTMOptions = RTMOptions(), rtm: RTMWebSocket? = nil) {
self.rtm = SKRTMAPI(withAPIToken: token, options: options, rtm: rtm)
self.rtm?.adapter = self
clients[token] = client
self.rtm?.connect()
}
public func addServer(_ server: SlackKitServer? = nil, responder: SlackKitResponder? = nil, oauth: OAuthConfig? = nil) {
var responder: SlackKitResponder = responder ?? SlackKitResponder(routes: [])
if let oauth = oauth {
responder.routes.append(oauthRequestRoute(config: oauth))
}
self.server = SKServer(server: server, responder: responder)
self.server?.start()
}
private func oauthRequestRoute(config: OAuthConfig) -> RequestRoute {
let oauth = OAuthMiddleware(config: config) { authorization in
// User
if let token = authorization.accessToken {
self.webAPI = WebAPI(token: token)
}
// Bot User
if let token = authorization.bot?.botToken {
self.webAPI = WebAPI(token: token)
self.rtm = SKRTMAPI(withAPIToken: token, options: RTMOptions(), rtm: nil)
self.rtm?.adapter = self
self.clients[token] = Client()
self.rtm?.connect()
}
}
return RequestRoute(path: "/oauth", middleware: oauth)
}
//MARK: - RTM Adapter
public func initialSetup(json: [String: Any], instance: SKRTMAPI) {
clients[instance.token]?.initialSetup(JSON: json)
}
public func notificationForEvent(_ event: Event, type: EventType, instance: SKRTMAPI) {
let client = clients[instance.token]
client?.notificationForEvent(event, type: type)
executeCallbackForEvent(event, type: type, client: client)
}
//MARK: - Callbacks
public func notificationForEvent(_ type: EventType, event: @escaping EventClosure) {
callbacks.append((type, event))
}
private func executeCallbackForEvent(_ event: Event, type: EventType, client: Client?) {
let cbs = callbacks.filter {$0.0 == type}
for callback in cbs {
callback.1(event, client)
}
}
}