Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58bade8ec9 | |||
| 7d7f5c40d6 | |||
| c9fd54d106 | |||
| 2f1ce799ad | |||
| 7f02f4cf99 | |||
| a9066c0d0a | |||
| 29739dba74 | |||
| d60a7094a4 | |||
| 7edd4210f6 | |||
| 2abaecbd14 | |||
| 346499e03b | |||
| bc72a52bf5 |
@@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
   [](https://github.com/apple/swift-package-manager) [](https://github.com/Carthage/Carthage)
|
||||
   [](https://cocoapods.org) [](https://github.com/Carthage/Carthage) [](https://github.com/apple/swift-package-manager)
|
||||
## SlackKit: A Swift Slack Client Library
|
||||
### Description
|
||||
|
||||
@@ -18,11 +18,11 @@ To build the SlackKit project directly, first build the dependencies using Carth
|
||||
Add SlackKit to your pod file:
|
||||
```
|
||||
use_frameworks!
|
||||
pod 'SlackKit', '~> 3.1.2'
|
||||
pod 'SlackKit', '~> 3.1.7'
|
||||
```
|
||||
and run
|
||||
```
|
||||
# Use CocoaPods version >= 1.1.0.rc.2 (gem install cocoapods --pre)
|
||||
# Use CocoaPods version >= 1.1.0
|
||||
pod install
|
||||
```
|
||||
|
||||
@@ -36,10 +36,6 @@ and run
|
||||
```
|
||||
carthage bootstrap
|
||||
```
|
||||
**Note:** SlackKit currently takes a _long_ time for the compiler to compile with optimizations turned on. I'm currently exploring a potential fix for this issue. In the meantime, you may want to skip the waiting and build it in the debug configuration instead:
|
||||
```
|
||||
carthage bootstrap --configuration "Debug"
|
||||
```
|
||||
|
||||
Drag the built `SlackKit.framework` into your Xcode project.
|
||||
|
||||
@@ -88,7 +84,7 @@ incoming.postMessage(message)
|
||||
#### 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)), initialize a webhook server with the token for the slash command, a configured route, and a response.
|
||||
```swift
|
||||
let response = Response(text: "Hello, World!", responseType: .InChannel)
|
||||
let response = Response(text: "Hello, World!", responseType: .inChannel)
|
||||
let webhook = WebhookServer(token: "SLASH-COMMAND-TOKEN", route: "hello_world", response: response)
|
||||
webhook.start()
|
||||
```
|
||||
@@ -111,7 +107,7 @@ let attachment = Attachment(fallback: "Hello World Attachment", title: "Attachme
|
||||
To act on message actions, initialize an instance of the `MessageActionServer` using your app’s verification token, your specified interactive messages request URL route, and a `MessageActionResponder`:
|
||||
```swift
|
||||
let action = Action(name: "hello_world", text: "Hello, World!")
|
||||
let response = Response(text: "Hello, 🌎!", responseType: .InChannel)
|
||||
let response = Response(text: "Hello, 🌎!", responseType: .inChannel)
|
||||
let responder = MessageActionResponder(responses: [(action, response)])
|
||||
let server = MessageActionServer(token: "SLACK-APP-VERIFICATION-TOKEN", route: "actions", responder: responder)
|
||||
server.start()
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SlackKit"
|
||||
s.version = "3.1.2"
|
||||
s.version = "3.1.7"
|
||||
s.summary = "a Slack client library for OS X, iOS, and tvOS written in Swift"
|
||||
s.homepage = "https://github.com/pvzig/SlackKit"
|
||||
s.license = 'MIT'
|
||||
|
||||
@@ -39,7 +39,7 @@ public struct Action {
|
||||
confirm = Confirm(confirm:action?["confirm"] as? [String: Any])
|
||||
}
|
||||
|
||||
public init(name: String, text: String, style: ActionStyle = .Default, value: String? = nil, confirm: Confirm? = nil) {
|
||||
public init(name: String, text: String, style: ActionStyle = .defaultStyle, value: String? = nil, confirm: Confirm? = nil) {
|
||||
self.type = "button"
|
||||
self.name = name
|
||||
self.text = text
|
||||
@@ -92,12 +92,12 @@ public struct Action {
|
||||
}
|
||||
|
||||
public enum ActionStyle: String {
|
||||
case Default = "default"
|
||||
case Primary = "primary"
|
||||
case Danger = "danger"
|
||||
case defaultStyle = "default"
|
||||
case primary = "primary"
|
||||
case danger = "danger"
|
||||
}
|
||||
|
||||
public enum ResponseType: String {
|
||||
case InChannel = "in_channel"
|
||||
case Ephemeral = "ephemeral"
|
||||
case inChannel = "in_channel"
|
||||
case ephemeral = "ephemeral"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public struct Attachment {
|
||||
}
|
||||
|
||||
public enum AttachmentColor: String {
|
||||
case Good = "good"
|
||||
case Warning = "warning"
|
||||
case Danger = "danger"
|
||||
case good = "good"
|
||||
case warning = "warning"
|
||||
case danger = "danger"
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public extension Client {
|
||||
if let channel = channel {
|
||||
success(channel.0)
|
||||
} else {
|
||||
webAPI.openIM(id, success: success, failure: failure)
|
||||
webAPI.openIM(userID: id, success: success, failure: failure)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public final class Client: WebSocketDelegate {
|
||||
}
|
||||
|
||||
internal var webSocket: WebSocket?
|
||||
fileprivate let pingPongQueue = DispatchQueue(label: "com.launchsoft.SlackKit")
|
||||
private let pingPongQueue = DispatchQueue(label: "com.launchsoft.SlackKit")
|
||||
internal var ping: Double?
|
||||
internal var pong: Double?
|
||||
internal var options: ClientOptions?
|
||||
@@ -75,7 +75,7 @@ public final class Client: WebSocketDelegate {
|
||||
|
||||
public func connect(options: ClientOptions = ClientOptions()) {
|
||||
self.options = options
|
||||
webAPI.rtmStart(options.simpleLatest, noUnreads: options.noUnreads, mpimAware: options.mpimAware, success: {(response) in
|
||||
webAPI.rtmStart(simpleLatest: options.simpleLatest, noUnreads: options.noUnreads, mpimAware: options.mpimAware, success: {(response) in
|
||||
guard let socketURL = response["url"] as? String, let url = URL(string: socketURL) else {
|
||||
return
|
||||
}
|
||||
@@ -101,7 +101,7 @@ public final class Client: WebSocketDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func format(message: String, channel: String) throws -> Data {
|
||||
private func format(message: String, channel: String) throws -> Data {
|
||||
let json: [String: Any] = [
|
||||
"id": Date().slackTimestamp,
|
||||
"type": "message",
|
||||
@@ -112,7 +112,7 @@ public final class Client: WebSocketDelegate {
|
||||
return try JSONSerialization.data(withJSONObject: json, options: [])
|
||||
}
|
||||
|
||||
fileprivate func addSentMessage(_ dictionary: [String: Any]) {
|
||||
private func addSentMessage(_ dictionary: [String: Any]) {
|
||||
var message = dictionary
|
||||
guard let id = message["id"] as? NSNumber else {
|
||||
return
|
||||
@@ -125,7 +125,7 @@ public final class Client: WebSocketDelegate {
|
||||
}
|
||||
|
||||
//MARK: - RTM Ping
|
||||
fileprivate func pingRTMServerAt(interval: TimeInterval) {
|
||||
private func pingRTMServerAt(interval: TimeInterval) {
|
||||
let delay = DispatchTime.now() + Double(Int64(interval * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
|
||||
pingPongQueue.asyncAfter(deadline: delay, execute: {
|
||||
guard self.connected && self.timeoutCheck() else {
|
||||
@@ -137,7 +137,7 @@ public final class Client: WebSocketDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func sendRTMPing() {
|
||||
private func sendRTMPing() {
|
||||
guard connected else {
|
||||
return
|
||||
}
|
||||
@@ -154,7 +154,7 @@ public final class Client: WebSocketDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func timeoutCheck() -> Bool {
|
||||
private func timeoutCheck() -> Bool {
|
||||
if let pong = pong, let ping = ping, let timeout = options?.timeout {
|
||||
if pong - ping < timeout {
|
||||
return true
|
||||
@@ -168,7 +168,7 @@ public final class Client: WebSocketDelegate {
|
||||
}
|
||||
|
||||
//MARK: - Client setup
|
||||
fileprivate func initialSetup(JSON: [String: Any]) {
|
||||
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])
|
||||
@@ -181,28 +181,28 @@ public final class Client: WebSocketDelegate {
|
||||
enumerateSubteams(JSON["subteams"] as? [String: Any])
|
||||
}
|
||||
|
||||
fileprivate func addUser(_ aUser: [String: Any]) {
|
||||
private func addUser(_ aUser: [String: Any]) {
|
||||
let user = User(user: aUser)
|
||||
if let id = user.id {
|
||||
users[id] = user
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func addChannel(_ aChannel: [String: Any]) {
|
||||
private func addChannel(_ aChannel: [String: Any]) {
|
||||
let channel = Channel(channel: aChannel)
|
||||
if let id = channel.id {
|
||||
channels[id] = channel
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func addBot(_ aBot: [String: Any]) {
|
||||
private func addBot(_ aBot: [String: Any]) {
|
||||
let bot = Bot(bot: aBot)
|
||||
if let id = bot.id {
|
||||
bots[id] = bot
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func enumerateSubteams(_ subteams: [String: Any]?) {
|
||||
private func enumerateSubteams(_ subteams: [String: Any]?) {
|
||||
if let subteams = subteams {
|
||||
if let all = subteams["all"] as? [[String: Any]] {
|
||||
for item in all {
|
||||
@@ -222,7 +222,7 @@ public final class Client: WebSocketDelegate {
|
||||
}
|
||||
|
||||
// MARK: - Utilities
|
||||
fileprivate func enumerateObjects(_ array: [Any]?, initalizer: ([String: Any])-> Void) {
|
||||
private func enumerateObjects(_ array: [Any]?, initalizer: ([String: Any])-> Void) {
|
||||
if let array = array {
|
||||
for object in array {
|
||||
if let dictionary = object as? [String: Any] {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
// THE SOFTWARE.
|
||||
|
||||
internal enum EventType: String {
|
||||
|
||||
case hello = "hello"
|
||||
case message = "message"
|
||||
case userTyping = "user_typing"
|
||||
@@ -97,7 +96,6 @@ internal enum EventType: String {
|
||||
}
|
||||
|
||||
internal enum MessageSubtype: String {
|
||||
|
||||
case botMessage = "bot_message"
|
||||
case meMessage = "me_message"
|
||||
case messageChanged = "message_changed"
|
||||
|
||||
@@ -39,19 +39,3 @@ internal extension String {
|
||||
return escapedString
|
||||
}
|
||||
}
|
||||
|
||||
internal extension Dictionary where Key: ExpressibleByStringLiteral, Value: Any {
|
||||
|
||||
var requestStringFromParameters: String {
|
||||
var requestString = ""
|
||||
for key in self.keys {
|
||||
if let value = self[key] as? String, let encodedValue = value.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed) {
|
||||
requestString += "&\(key)=\(encodedValue)"
|
||||
} else if let value = self[key] {
|
||||
requestString += "&\(key)=\(value)"
|
||||
}
|
||||
}
|
||||
return requestString
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public struct IncomingWebhook {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func jsonBody(_ response: [String: Any]) -> [String: Any] {
|
||||
private func jsonBody(_ response: [String: Any]) -> [String: Any] {
|
||||
var json = response
|
||||
json["channel"] = channel
|
||||
json["username"] = username
|
||||
|
||||
@@ -27,22 +27,18 @@ internal struct NetworkInterface {
|
||||
|
||||
private let apiUrl = "https://slack.com/api/"
|
||||
|
||||
internal func request(_ endpoint: Endpoint, token: String? = nil, parameters: [String: Any]?, successClosure: @escaping ([String: Any])->Void, errorClosure: @escaping (SlackError)->Void) {
|
||||
var requestString = "\(apiUrl)\(endpoint.rawValue)?"
|
||||
if let token = token {
|
||||
requestString += "token=\(token)"
|
||||
internal func request(_ endpoint: Endpoint, parameters: [String: Any?], successClosure: @escaping ([String: Any])->Void, errorClosure: @escaping (SlackError)->Void) {
|
||||
var components = URLComponents(string: "\(apiUrl)\(endpoint.rawValue)")
|
||||
if parameters.count > 0 {
|
||||
components?.queryItems = filterNilParameters(parameters).map { URLQueryItem(name: $0.0, value: "\($0.1)") }
|
||||
}
|
||||
if let params = parameters {
|
||||
requestString += params.requestStringFromParameters
|
||||
}
|
||||
guard let url = URL(string: requestString) else {
|
||||
guard let url = components?.url else {
|
||||
errorClosure(SlackError.clientNetworkError)
|
||||
return
|
||||
}
|
||||
let request = URLRequest(url:url)
|
||||
|
||||
URLSession.shared.dataTask(with: request) {
|
||||
(data, response, internalError) -> Void in
|
||||
let request = URLRequest(url: url)
|
||||
|
||||
URLSession.shared.dataTask(with: request) {(data, response, internalError) in
|
||||
do {
|
||||
successClosure(try self.handleResponse(data, response: response, internalError: internalError))
|
||||
} catch let error {
|
||||
@@ -62,8 +58,7 @@ internal struct NetworkInterface {
|
||||
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = data
|
||||
|
||||
URLSession.shared.dataTask(with: request) {
|
||||
(data, response, internalError) -> Void in
|
||||
URLSession.shared.dataTask(with: request) {(data, response, internalError) in
|
||||
if internalError == nil {
|
||||
success(true)
|
||||
} else {
|
||||
@@ -72,12 +67,12 @@ internal struct NetworkInterface {
|
||||
}.resume()
|
||||
}
|
||||
|
||||
internal func uploadRequest(_ token: String, data: Data, parameters: [String: Any]?, successClosure: @escaping ([String: Any])->Void, errorClosure: @escaping (SlackError)->Void) {
|
||||
var requestString = "\(apiUrl)\(Endpoint.filesUpload.rawValue)?token=\(token)"
|
||||
if let params = parameters {
|
||||
requestString = requestString + params.requestStringFromParameters
|
||||
internal func uploadRequest(data: Data, parameters: [String: Any?], successClosure: @escaping ([String: Any])->Void, errorClosure: @escaping (SlackError)->Void) {
|
||||
var components = URLComponents(string: "\(apiUrl)\(Endpoint.filesUpload.rawValue)")
|
||||
if parameters.count > 0 {
|
||||
components?.queryItems = filterNilParameters(parameters).map { URLQueryItem(name: $0.0, value: "\($0.1)") }
|
||||
}
|
||||
guard let url = URL(string: requestString) else {
|
||||
guard let url = components?.url else {
|
||||
errorClosure(SlackError.clientNetworkError)
|
||||
return
|
||||
}
|
||||
@@ -87,9 +82,9 @@ internal struct NetworkInterface {
|
||||
let contentType = "multipart/form-data; boundary=" + boundaryConstant
|
||||
let boundaryStart = "--\(boundaryConstant)\r\n"
|
||||
let boundaryEnd = "--\(boundaryConstant)--\r\n"
|
||||
let contentDispositionString = "Content-Disposition: form-data; name=\"file\"; filename=\"\(parameters!["filename"])\"\r\n"
|
||||
let contentTypeString = "Content-Type: \(parameters!["filetype"])\r\n\r\n"
|
||||
|
||||
let 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 = Data()
|
||||
requestBodyData.append(boundaryStart.data(using: String.Encoding.utf8)!)
|
||||
requestBodyData.append(contentDispositionString.data(using: String.Encoding.utf8)!)
|
||||
@@ -100,9 +95,8 @@ internal struct NetworkInterface {
|
||||
|
||||
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
||||
request.httpBody = requestBodyData as Data
|
||||
|
||||
URLSession.shared.dataTask(with: request) {
|
||||
(data, response, internalError) -> Void in
|
||||
|
||||
URLSession.shared.dataTask(with: request) {(data, response, internalError) in
|
||||
do {
|
||||
successClosure(try self.handleResponse(data, response: response, internalError: internalError))
|
||||
} catch let error {
|
||||
@@ -148,4 +142,15 @@ internal struct NetworkInterface {
|
||||
private func randomBoundary() -> String {
|
||||
return String(format: "slackkit.boundary.%08x%08x", arc4random(), arc4random())
|
||||
}
|
||||
|
||||
//MARK: - Filter Nil Parameters
|
||||
private func filterNilParameters(_ parameters: [String: Any?]) -> [String: Any] {
|
||||
var finalParameters = [String: Any]()
|
||||
for (key, value) in parameters {
|
||||
if let unwrapped = value {
|
||||
finalParameters[key] = unwrapped
|
||||
}
|
||||
}
|
||||
return finalParameters
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,14 +30,14 @@ internal protocol OAuthDelegate {
|
||||
|
||||
public struct OAuthServer {
|
||||
|
||||
fileprivate let oauthURL = "https://slack.com/oauth/authorize"
|
||||
|
||||
fileprivate let http = HttpServer()
|
||||
fileprivate let clientID: String
|
||||
fileprivate let clientSecret: String
|
||||
fileprivate let state: String?
|
||||
fileprivate let redirectURI: String?
|
||||
fileprivate var delegate: OAuthDelegate?
|
||||
private let oauthURL = "https://slack.com/oauth/authorize"
|
||||
|
||||
private let http = HttpServer()
|
||||
private let clientID: String
|
||||
private let clientSecret: String
|
||||
private let state: String?
|
||||
private let redirectURI: String?
|
||||
private var delegate: OAuthDelegate?
|
||||
|
||||
internal init(clientID: String, clientSecret: String, state: String? = nil, redirectURI: String? = nil, port:in_port_t = 8080, forceIPV4: Bool = false, delegate: OAuthDelegate? = nil) throws {
|
||||
self.clientID = clientID
|
||||
@@ -61,12 +61,12 @@ public struct OAuthServer {
|
||||
http.stop()
|
||||
}
|
||||
|
||||
fileprivate func oauthRoute() {
|
||||
private func oauthRoute() {
|
||||
http["/oauth"] = { request in
|
||||
guard let response = AuthorizeResponse(queryParameters: request.queryParams), response.state == self.state else {
|
||||
return .badRequest(.text("Bad request."))
|
||||
}
|
||||
WebAPI.oauthAccess(self.clientID, clientSecret: self.clientSecret, code: response.code, redirectURI: self.redirectURI, success: {(response) in
|
||||
WebAPI.oauthAccess(clientID: self.clientID, clientSecret: self.clientSecret, code: response.code, redirectURI: self.redirectURI, success: {(response) in
|
||||
self.delegate?.userAuthed(OAuthResponse(response: response))
|
||||
}, failure: {(error) in
|
||||
print("Authorization failed")
|
||||
@@ -78,13 +78,13 @@ public struct OAuthServer {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func oauthURLRequest(_ authorize: AuthorizeRequest) -> URLRequest? {
|
||||
var requestString = "\(oauthURL)?client_id=\(authorize.clientID)"
|
||||
requestString += authorize.parameters.requestStringFromParameters
|
||||
guard let url = URL(string: requestString) else {
|
||||
private func oauthURLRequest(_ authorize: AuthorizeRequest) -> URLRequest? {
|
||||
var components = URLComponents(string: "\(oauthURL)")
|
||||
components?.queryItems = [URLQueryItem(name: "client_id", value: "\(authorize.clientID)")]
|
||||
guard let url = components?.url else {
|
||||
return nil
|
||||
}
|
||||
return URLRequest(url:url)
|
||||
return URLRequest(url: url)
|
||||
}
|
||||
|
||||
public func authorizeRequest(_ scope:[Scope], redirectURI: String, state: String = "slackkit", team: String? = nil) -> URLRequest? {
|
||||
|
||||
@@ -27,7 +27,7 @@ public final class SlackKit: OAuthDelegate {
|
||||
|
||||
internal(set) public var oauth: OAuthServer?
|
||||
internal(set) public var clients: [String: Client] = [:]
|
||||
fileprivate let clientOptions: ClientOptions
|
||||
private let clientOptions: ClientOptions
|
||||
// Initalization block
|
||||
public var onClientInitalization: ((Client) -> Void)?
|
||||
|
||||
|
||||
+387
-460
File diff suppressed because it is too large
Load Diff
@@ -39,7 +39,7 @@ open class WebhookServer: Server {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func replyForResponse(_ response: Response) -> Reply {
|
||||
private func replyForResponse(_ response: Response) -> Reply {
|
||||
if response.attachments == nil && response.responseType == nil {
|
||||
return Reply.text(body: response.text)
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.1.5</string>
|
||||
<string>3.1.7</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.1.5</string>
|
||||
<string>3.1.7</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.1.5</string>
|
||||
<string>3.1.7</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
Reference in New Issue
Block a user