Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3823aee4cf | |||
| b95391b03b | |||
| 56be11a950 | |||
| f6f4252075 | |||
| a16a78d322 | |||
| ab751a0304 | |||
| 8e46cbb105 | |||
| e178c3748a | |||
| a19f6b3088 | |||
| 6b385bd29c |
+2
-1
@@ -40,4 +40,5 @@ Carthage/Build
|
||||
# Swift Package Manager
|
||||
.swiftpm/
|
||||
|
||||
*.resolved
|
||||
*.resolved
|
||||
.vscode
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
5.2.4
|
||||
5.5.2
|
||||
@@ -44,6 +44,8 @@ Add `SlackKit` to your `Cartfile`:
|
||||
github "pvzig/SlackKit"
|
||||
```
|
||||
|
||||
**SlackKit is now using .xcframeworks. When building your dependencies with carthage, please specify a platform: `carthage bootstrap --use-xcframeworks --platform macos`**
|
||||
|
||||
#### CocoaPods
|
||||
Add `SlackKit` to your `Podfile`:
|
||||
|
||||
|
||||
@@ -220,6 +220,10 @@ extension Action.Option: Codable {
|
||||
|
||||
extension Action.Option.CodingKeys: CodingKey { }
|
||||
|
||||
extension Action: Equatable { }
|
||||
extension Action.Confirm: Equatable { }
|
||||
extension Action.Option: Equatable { }
|
||||
|
||||
public enum ActionStyle: String, Codable {
|
||||
case defaultStyle = "default"
|
||||
case primary = "primary"
|
||||
|
||||
@@ -22,6 +22,28 @@
|
||||
// THE SOFTWARE.
|
||||
|
||||
public struct Attachment {
|
||||
fileprivate enum CodingKeys: String {
|
||||
case fallback
|
||||
case callbackID = "callback_id"
|
||||
case type = "attachment_type"
|
||||
case color
|
||||
case pretext
|
||||
case authorName = "author_name"
|
||||
case authorLink = "author_link"
|
||||
case authorIcon = "author_icon"
|
||||
case title
|
||||
case titleLink = "title_link"
|
||||
case text
|
||||
case fields
|
||||
case actions
|
||||
case imageURL = "image_url"
|
||||
case thumbURL = "thumb_url"
|
||||
case footer
|
||||
case footerIcon = "footer_icon"
|
||||
case ts
|
||||
case markdownEnabledFields = "mrkdwn_in"
|
||||
}
|
||||
|
||||
public let fallback: String?
|
||||
public let callbackID: String?
|
||||
public let type: String?
|
||||
@@ -43,25 +65,25 @@ public struct Attachment {
|
||||
public let markdownEnabledFields: Set<AttachmentTextField>?
|
||||
|
||||
public init(attachment: [String: Any]?) {
|
||||
fallback = attachment?["fallback"] as? String
|
||||
callbackID = attachment?["callback_id"] as? String
|
||||
type = attachment?["attachment_type"] 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
|
||||
footer = attachment?["footer"] as? String
|
||||
footerIcon = attachment?["footer_icon"] as? String
|
||||
ts = attachment?["ts"] as? Int
|
||||
fields = (attachment?["fields"] as? [[String: Any]])?.map { AttachmentField(field: $0) }
|
||||
actions = (attachment?["actions"] as? [[String: Any]])?.map { Action(action: $0) }
|
||||
markdownEnabledFields = (attachment?["mrkdwn_in"] as? [String]).map { Set($0.compactMap(AttachmentTextField.init)) }
|
||||
fallback = attachment?[CodingKeys.fallback] as? String
|
||||
callbackID = attachment?[CodingKeys.callbackID] as? String
|
||||
type = attachment?[CodingKeys.type] as? String
|
||||
color = attachment?[CodingKeys.color] as? String
|
||||
pretext = attachment?[CodingKeys.pretext] as? String
|
||||
authorName = attachment?[CodingKeys.authorName] as? String
|
||||
authorLink = attachment?[CodingKeys.authorLink] as? String
|
||||
authorIcon = attachment?[CodingKeys.authorIcon] as? String
|
||||
title = attachment?[CodingKeys.title] as? String
|
||||
titleLink = attachment?[CodingKeys.titleLink] as? String
|
||||
text = attachment?[CodingKeys.text] as? String
|
||||
fields = (attachment?[CodingKeys.fields] as? [[String: Any]])?.map { AttachmentField(field: $0) }
|
||||
actions = (attachment?[CodingKeys.actions] as? [[String: Any]])?.map { Action(action: $0) }
|
||||
imageURL = attachment?[CodingKeys.imageURL] as? String
|
||||
thumbURL = attachment?[CodingKeys.thumbURL] as? String
|
||||
footer = attachment?[CodingKeys.footer] as? String
|
||||
footerIcon = attachment?[CodingKeys.footerIcon] as? String
|
||||
ts = attachment?[CodingKeys.ts] as? Int
|
||||
markdownEnabledFields = (attachment?[CodingKeys.markdownEnabledFields] as? [String]).map { Set($0.compactMap(AttachmentTextField.init)) }
|
||||
}
|
||||
|
||||
public init(
|
||||
@@ -135,12 +157,62 @@ public enum AttachmentColor: String {
|
||||
case good, warning, danger
|
||||
}
|
||||
|
||||
public enum AttachmentTextField: String {
|
||||
case fallback = "fallback"
|
||||
case pretext = "pretext"
|
||||
public enum AttachmentTextField: String, Codable {
|
||||
case fallback
|
||||
case pretext
|
||||
case authorName = "author_name"
|
||||
case title = "title"
|
||||
case text = "text"
|
||||
case fields = "fields"
|
||||
case footer = "footer"
|
||||
case title
|
||||
case text
|
||||
case fields
|
||||
case footer
|
||||
}
|
||||
|
||||
extension Attachment: Codable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
fallback = try values.decodeIfPresent(String.self, forKey: .fallback)
|
||||
callbackID = try values.decodeIfPresent(String.self, forKey: .callbackID)
|
||||
type = try values.decodeIfPresent(String.self, forKey: .type)
|
||||
color = try values.decodeIfPresent(String.self, forKey: .color)
|
||||
pretext = try values.decodeIfPresent(String.self, forKey: .pretext)
|
||||
authorName = try values.decodeIfPresent(String.self, forKey: .authorName)
|
||||
authorLink = try values.decodeIfPresent(String.self, forKey: .authorLink)
|
||||
authorIcon = try values.decodeIfPresent(String.self, forKey: .authorIcon)
|
||||
title = try values.decodeIfPresent(String.self, forKey: .title)
|
||||
titleLink = try values.decodeIfPresent(String.self, forKey: .titleLink)
|
||||
text = try values.decodeIfPresent(String.self, forKey: .text)
|
||||
fields = try values.decodeIfPresent([AttachmentField].self, forKey: .fields)
|
||||
actions = try values.decodeIfPresent([Action].self, forKey: .actions)
|
||||
imageURL = try values.decodeIfPresent(String.self, forKey: .imageURL)
|
||||
thumbURL = try values.decodeIfPresent(String.self, forKey: .thumbURL)
|
||||
footer = try values.decodeIfPresent(String.self, forKey: .footer)
|
||||
footerIcon = try values.decodeIfPresent(String.self, forKey: .footerIcon)
|
||||
ts = try values.decodeIfPresent(Int.self, forKey: .ts)
|
||||
markdownEnabledFields = (try values.decodeIfPresent([AttachmentTextField].self, forKey: .markdownEnabledFields)).map { Set($0) }
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(fallback, forKey: .fallback)
|
||||
try container.encode(callbackID, forKey: .callbackID)
|
||||
try container.encode(type, forKey: .type)
|
||||
try container.encode(color, forKey: .color)
|
||||
try container.encode(pretext, forKey: .pretext)
|
||||
try container.encode(authorName, forKey: .authorName)
|
||||
try container.encode(authorLink, forKey: .authorLink)
|
||||
try container.encode(authorIcon, forKey: .authorIcon)
|
||||
try container.encode(title, forKey: .title)
|
||||
try container.encode(titleLink, forKey: .titleLink)
|
||||
try container.encode(text, forKey: .text)
|
||||
try container.encode(fields, forKey: .fields)
|
||||
try container.encode(actions, forKey: .actions)
|
||||
try container.encode(imageURL, forKey: .imageURL)
|
||||
try container.encode(thumbURL, forKey: .thumbURL)
|
||||
try container.encode(footer, forKey: .footer)
|
||||
try container.encode(footerIcon, forKey: .footerIcon)
|
||||
try container.encode(ts, forKey: .ts)
|
||||
try container.encode(markdownEnabledFields, forKey: .markdownEnabledFields)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attachment.CodingKeys: CodingKey { }
|
||||
|
||||
@@ -70,3 +70,5 @@ extension AttachmentField: Codable {
|
||||
}
|
||||
|
||||
extension AttachmentField.CodingKeys: CodingKey { }
|
||||
|
||||
extension AttachmentField: Equatable { }
|
||||
|
||||
@@ -38,13 +38,13 @@ public protocol RTMWebSocket {
|
||||
func sendMessage(_ message: String) throws
|
||||
}
|
||||
|
||||
public protocol RTMAdapter: class {
|
||||
public protocol RTMAdapter: AnyObject {
|
||||
func initialSetup(json: [String: Any], instance: SKRTMAPI)
|
||||
func notificationForEvent(_ event: Event, type: EventType, instance: SKRTMAPI)
|
||||
func connectionClosed(with error: Error, instance: SKRTMAPI)
|
||||
}
|
||||
|
||||
public protocol RTMDelegate: class {
|
||||
public protocol RTMDelegate: AnyObject {
|
||||
func didConnect()
|
||||
func disconnected()
|
||||
func receivedMessage(_ message: String)
|
||||
@@ -76,34 +76,17 @@ public final class SKRTMAPI: RTMDelegate {
|
||||
self.rtm.delegate = self
|
||||
}
|
||||
|
||||
public func connect(withInfo: Bool = true) {
|
||||
if withInfo {
|
||||
WebAPI.rtmStart(
|
||||
token: token,
|
||||
batchPresenceAware: options.noUnreads,
|
||||
mpimAware: options.mpimAware,
|
||||
noLatest: options.noLatest,
|
||||
noUnreads: options.noUnreads,
|
||||
presenceSub: options.presenceSub,
|
||||
simpleLatest: options.simpleLatest,
|
||||
success: {(response) in
|
||||
self.connectWithResponse(response)
|
||||
}, failure: { (error) in
|
||||
self.adapter?.connectionClosed(with: error, instance: self)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
WebAPI.rtmConnect(
|
||||
token: token,
|
||||
batchPresenceAware: options.batchPresenceAware,
|
||||
presenceSub: options.presenceSub,
|
||||
success: {(response) in
|
||||
self.connectWithResponse(response)
|
||||
}, failure: { (error) in
|
||||
self.adapter?.connectionClosed(with: error, instance: self)
|
||||
}
|
||||
)
|
||||
}
|
||||
public func connect() {
|
||||
WebAPI.rtmConnect(
|
||||
token: token,
|
||||
batchPresenceAware: options.batchPresenceAware,
|
||||
presenceSub: options.presenceSub,
|
||||
success: {(response) in
|
||||
self.connectWithResponse(response)
|
||||
}, failure: { (error) in
|
||||
self.adapter?.connectionClosed(with: error, instance: self)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
|
||||
@@ -58,6 +58,7 @@ extension HttpRequest {
|
||||
return try! Request(
|
||||
method: HTTPMethod.custom(named: method),
|
||||
path: path,
|
||||
queryPairs: queryParams,
|
||||
body: String(bytes: body, encoding: .utf8) ?? "",
|
||||
headers: HTTPHeaders(headers: headers.map ({ Header(name: $0.key, value: $0.value) }))
|
||||
)
|
||||
|
||||
@@ -17,8 +17,10 @@ import Foundation
|
||||
public protocol RequestType {
|
||||
/// The HTTP request body.
|
||||
var body: Data { get }
|
||||
/// The HTTP request path, including query string and any fragments.
|
||||
/// The HTTP request path
|
||||
var path: String { get }
|
||||
/// The HTTP request query pairs
|
||||
var queryPairs: [(String, String)] { get }
|
||||
/// The HTTP request method.
|
||||
var method: HTTPMethod { get }
|
||||
/// The HTTP request headers.
|
||||
@@ -30,14 +32,16 @@ public struct Request: RequestType {
|
||||
|
||||
public var method: HTTPMethod
|
||||
public var path: String
|
||||
public var queryPairs: [(String, String)]
|
||||
public var body: Data
|
||||
public var headers: HTTPHeaders
|
||||
|
||||
/// Create a Request
|
||||
/// Throws an error if the body parameter cannot be converted to Data
|
||||
public init(method: HTTPMethod, path: String, body: String, headers: HTTPHeaders) throws {
|
||||
public init(method: HTTPMethod, path: String, queryPairs: [(String, String)], body: String, headers: HTTPHeaders) throws {
|
||||
self.method = method
|
||||
self.path = path
|
||||
self.queryPairs = queryPairs
|
||||
guard let data = body.data(using: .utf8) else {
|
||||
throw TitanError.dataConversion
|
||||
}
|
||||
@@ -46,9 +50,10 @@ public struct Request: RequestType {
|
||||
}
|
||||
|
||||
/// Create a Request
|
||||
public init(method: HTTPMethod, path: String, body: Data, headers: HTTPHeaders) {
|
||||
public init(method: HTTPMethod, path: String, queryPairs: [(String, String)], body: Data, headers: HTTPHeaders) {
|
||||
self.method = method
|
||||
self.path = path
|
||||
self.queryPairs = queryPairs
|
||||
self.body = body
|
||||
self.headers = headers
|
||||
}
|
||||
@@ -57,7 +62,7 @@ public struct Request: RequestType {
|
||||
extension Request {
|
||||
/// Create a Request from a RequestType
|
||||
public init(request: RequestType) {
|
||||
self.init(method: request.method, path: request.path, body: request.body, headers: request.headers)
|
||||
self.init(method: request.method, path: request.path, queryPairs: request.queryPairs, body: request.body, headers: request.headers)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,43 +14,6 @@
|
||||
import Foundation
|
||||
|
||||
public extension RequestType {
|
||||
/// The pairs of keys and values in the query string of the `RequestType`s path.
|
||||
/// Complexity: 0(n) on all invocations.
|
||||
var queryPairs: [(key: String, value: String)] {
|
||||
// Ensure there is a query string, otherwise return
|
||||
guard let indexOfQuery = self.path.firstIndex(of: "?") else {
|
||||
return []
|
||||
}
|
||||
|
||||
let query = self.path.suffix(from: indexOfQuery).dropFirst()
|
||||
// Create an array of the individual query pairs, e.g. ["foo=bar", "baz=qux"]
|
||||
let pairs = query.split(separator: "&")
|
||||
|
||||
// Decode `foo=bar` -> `(key: "foo", value: "bar")`, percent decoding any values along the way
|
||||
return pairs.map { pair -> (key: String, value: String) in
|
||||
// Separate the query pair into an array, e.g. "foo=bar" -> ["foo", "bar"]
|
||||
let comps = pair.split(separator: "=")
|
||||
// Split returns an array of subsequences which should conform to StringProtocol, however on Linux StringProtocol is out of date.
|
||||
// Workaround for https://bugs.swift.org/browse/SR-5727 by converting to a String directly
|
||||
.map(String.init)
|
||||
.map {
|
||||
// Percent encoding mandates that "%20" = <space>"
|
||||
// however, many applications use "+" to mean space as well, so decode those (before we decode any percent-encoded plus signs!)
|
||||
return $0.replacingOccurrences(of: "+", with: " ")
|
||||
}.map {
|
||||
return $0.removingPercentEncoding ?? ""
|
||||
}
|
||||
switch comps.count {
|
||||
case 1: // "?foo="
|
||||
return (key: String(comps[0]), value: "")
|
||||
case 2: // "?foo=bar"
|
||||
return (key: String(comps[0]), value: String(comps[1]))
|
||||
default: // "?"
|
||||
return (key: "", value: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the query string as a dictionary, with case sensitive keys.
|
||||
/// Complexity: 0(n) on all invocations.
|
||||
var query: [String: String] {
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
import Foundation
|
||||
|
||||
extension Request {
|
||||
public init(_ method: HTTPMethod = .get, _ path: String = "/", _ body: String = "", _ headers: HTTPHeaders = HTTPHeaders()) {
|
||||
self.init(method: method, path: path, body: body.data(using: .utf8) ?? Data(), headers: headers)
|
||||
public init(_ method: HTTPMethod = .get, _ path: String = "/", _ queryPairs: [(String, String)] = [], _ body: String = "", _ headers: HTTPHeaders = HTTPHeaders()) {
|
||||
self.init(method: method, path: path, queryPairs: queryPairs, body: body.data(using: .utf8) ?? Data(), headers: headers)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ SlackKit currently supports the a subset of the Slack Web API that is available
|
||||
| `reactions.get`|
|
||||
| `reactions.list`|
|
||||
| `reactions.remove`|
|
||||
| `rtm.start`|
|
||||
| `rtm.connect`|
|
||||
| `stars.add`|
|
||||
| `stars.remove`|
|
||||
| `team.info`|
|
||||
|
||||
@@ -26,34 +26,30 @@ public enum Endpoint: String {
|
||||
case authRevoke = "auth.revoke"
|
||||
case authTest = "auth.test"
|
||||
case channelsHistory = "channels.history"
|
||||
case channelsInfo = "channels.info"
|
||||
case channelsList = "channels.list"
|
||||
case channelsMark = "channels.mark"
|
||||
case channelsCreate = "channels.create"
|
||||
case channelsInvite = "channels.invite"
|
||||
case channelsJoin = "channels.join"
|
||||
case channelsLeave = "channels.leave"
|
||||
case channelsArchive = "channels.archive"
|
||||
case channelsUnarchive = "channels.unarchive"
|
||||
case channelsRename = "channels.rename"
|
||||
case channelsKick = "channels.kick"
|
||||
case channelsLeave = "channels.leave"
|
||||
case channelsRename = "channels.rename"
|
||||
case channelsSetPurpose = "channels.setPurpose"
|
||||
case channelsSetTopic = "channels.setTopic"
|
||||
case channelsUnarchive = "channels.unarchive"
|
||||
case chatDelete = "chat.delete"
|
||||
case chatPostMessage = "chat.postMessage"
|
||||
case chatPostEphemeral = "chat.postEphemeral"
|
||||
case chatMeMessage = "chat.meMessage"
|
||||
case chatPostEphemeral = "chat.postEphemeral"
|
||||
case chatPostMessage = "chat.postMessage"
|
||||
case chatUpdate = "chat.update"
|
||||
case conversationsList = "conversations.list"
|
||||
case conversationsReplies = "conversations.replies"
|
||||
case conversationsMembers = "conversations.members"
|
||||
case conversationsArchive = "conversations.archive"
|
||||
case conversationsCreate = "conversations.create"
|
||||
case conversationsHistory = "conversations.history"
|
||||
case conversationsList = "conversations.list"
|
||||
case conversationsMembers = "conversations.members"
|
||||
case conversationsOpen = "conversations.open"
|
||||
case conversationsReplies = "conversations.replies"
|
||||
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 filesCommentsEdit = "files.comments.edit"
|
||||
case filesDelete = "files.delete"
|
||||
case filesInfo = "files.info"
|
||||
case filesUpload = "files.upload"
|
||||
@@ -69,34 +65,42 @@ public enum Endpoint: String {
|
||||
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 oauthAccess = "oauth.access"
|
||||
case pinsList = "pins.list"
|
||||
case pinsAdd = "pins.add"
|
||||
case pinsList = "pins.list"
|
||||
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 rtmConnect = "rtm.connect"
|
||||
case searchAll = "search.all"
|
||||
case searchFiles = "search.files"
|
||||
case searchMessages = "search.messages"
|
||||
case starsAdd = "stars.add"
|
||||
case starsRemove = "stars.remove"
|
||||
case teamInfo = "team.info"
|
||||
case usersConversations = "users.conversations"
|
||||
case usersGetPresence = "users.getPresence"
|
||||
case usersInfo = "users.info"
|
||||
case usersList = "users.list"
|
||||
case usersConversations = "users.conversations"
|
||||
case usersLookupByEmail = "users.lookupByEmail"
|
||||
case usersProfileSet = "users.profile.set"
|
||||
case usersSetActive = "users.setActive"
|
||||
case usersSetPresence = "users.setPresence"
|
||||
case searchAll = "search.all"
|
||||
case searchFiles = "search.files"
|
||||
case searchMessages = "search.messages"
|
||||
|
||||
// MARK: - Deprecated endpoints
|
||||
case channelsArchive = "channels.archive"
|
||||
case channelsCreate = "channels.create"
|
||||
case channelsInfo = "channels.info"
|
||||
case channelsInvite = "channels.invite"
|
||||
case channelsJoin = "channels.join"
|
||||
case channelsList = "channels.list"
|
||||
case channelsMark = "channels.mark"
|
||||
case imOpen = "im.open"
|
||||
}
|
||||
|
||||
@@ -47,15 +47,23 @@ public struct NetworkInterface {
|
||||
|
||||
internal func request(
|
||||
_ endpoint: Endpoint,
|
||||
accessToken: String,
|
||||
parameters: [String: Any?],
|
||||
successClosure: @escaping ([String: Any]) -> Void,
|
||||
errorClosure: @escaping (SlackError) -> Void
|
||||
) {
|
||||
guard !accessToken.isEmpty else {
|
||||
errorClosure(.invalidAuth)
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = requestURL(for: endpoint, parameters: parameters) else {
|
||||
errorClosure(SlackError.clientNetworkError)
|
||||
return
|
||||
}
|
||||
let request = URLRequest(url: url)
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
|
||||
|
||||
session.dataTask(with: request) {(data, response, publicError) in
|
||||
do {
|
||||
@@ -89,6 +97,7 @@ public struct NetworkInterface {
|
||||
|
||||
internal func customRequest(
|
||||
_ url: String,
|
||||
token: String,
|
||||
data: Data,
|
||||
success: @escaping (Bool) -> Void,
|
||||
errorClosure: @escaping (SlackError) -> Void
|
||||
@@ -99,11 +108,12 @@ public struct NetworkInterface {
|
||||
}
|
||||
var request = URLRequest(url:url)
|
||||
request.httpMethod = "POST"
|
||||
let contentType = "application/json"
|
||||
let contentType = "application/json; charset: utf-8"
|
||||
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||
request.httpBody = data
|
||||
|
||||
session.dataTask(with: request) {(_, _, publicError) in
|
||||
session.dataTask(with: request) {(data, response, publicError) in
|
||||
if publicError == nil {
|
||||
success(true)
|
||||
} else {
|
||||
@@ -114,6 +124,7 @@ public struct NetworkInterface {
|
||||
|
||||
internal func uploadRequest(
|
||||
data: Data,
|
||||
accessToken: String,
|
||||
parameters: [String: Any?],
|
||||
successClosure: @escaping ([String: Any]) -> Void, errorClosure: @escaping (SlackError) -> Void
|
||||
) {
|
||||
@@ -130,6 +141,7 @@ public struct NetworkInterface {
|
||||
let boundaryConstant = randomBoundary()
|
||||
let contentType = "multipart/form-data; boundary=" + boundaryConstant
|
||||
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
|
||||
request.httpBody = requestBodyData(data: data, boundaryConstant: boundaryConstant, filename: filename, filetype: filetype)
|
||||
|
||||
session.dataTask(with: request) {(data, response, publicError) in
|
||||
|
||||
+204
-202
@@ -71,34 +71,6 @@ public final class WebAPI {
|
||||
|
||||
// MARK: - RTM
|
||||
extension WebAPI {
|
||||
public static func rtmStart(
|
||||
token: String,
|
||||
batchPresenceAware: Bool = false,
|
||||
mpimAware: Bool? = nil,
|
||||
noLatest: Bool = false,
|
||||
noUnreads: Bool? = nil,
|
||||
presenceSub: Bool = false,
|
||||
simpleLatest: Bool? = nil,
|
||||
success: ((_ response: [String: Any]) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] =
|
||||
[
|
||||
"token": token,
|
||||
"batch_presence_aware": batchPresenceAware,
|
||||
"mpim_aware": mpimAware,
|
||||
"no_latest": noLatest,
|
||||
"no_unreads": noUnreads,
|
||||
"presence_sub": presenceSub,
|
||||
"simple_latest": simpleLatest
|
||||
]
|
||||
NetworkInterface().request(.rtmStart, parameters: parameters, successClosure: {(response) in
|
||||
success?(response)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
public static func rtmConnect(
|
||||
token: String,
|
||||
batchPresenceAware: Bool = false,
|
||||
@@ -108,11 +80,10 @@ extension WebAPI {
|
||||
) {
|
||||
let parameters: [String: Any?] =
|
||||
[
|
||||
"token": token,
|
||||
"batch_presence_aware": batchPresenceAware,
|
||||
"presence_sub": presenceSub
|
||||
]
|
||||
NetworkInterface().request(.rtmConnect, parameters: parameters, successClosure: {(response) in
|
||||
NetworkInterface().request(.rtmConnect, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(response)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -124,7 +95,7 @@ extension WebAPI {
|
||||
// MARK: - Auth
|
||||
extension WebAPI {
|
||||
public func authenticationTest(success: AuthTestClosure?, failure: FailureClosure?) {
|
||||
networkInterface.request(.authTest, parameters: ["token": token], successClosure: { (response) in
|
||||
networkInterface.request(.authTest, accessToken: token, parameters: [:], successClosure: { (response) in
|
||||
success?(response["user_id"] as? String, response["team_id"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -142,8 +113,8 @@ extension WebAPI {
|
||||
success: SuccessClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = ["token": token, "test": test]
|
||||
NetworkInterface().request(.authRevoke, parameters: parameters, successClosure: { _ in
|
||||
let parameters: [String: Any?] = ["test": test]
|
||||
NetworkInterface().request(.authRevoke, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -174,56 +145,11 @@ extension WebAPI {
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func channelInfo(id: String, success: ChannelClosure?, failure: FailureClosure?) {
|
||||
info(.channelsInfo, type:.channel, id: id, success: {(channel) in
|
||||
success?(channel)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func channelsList(
|
||||
excludeArchived: Bool = false,
|
||||
excludeMembers: Bool = false,
|
||||
success: ((_ channels: [[String: Any]]?) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
list(.channelsList, type:.channel, excludeArchived: excludeArchived, excludeMembers: excludeMembers, success: {(channels) in
|
||||
success?(channels)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func markChannel(channel: String, timestamp: String, success: ((_ ts: String) -> Void)?, failure: FailureClosure?) {
|
||||
mark(.channelsMark, channel: channel, timestamp: timestamp, success: {(ts) in
|
||||
success?(ts)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func createChannel(channel: String, success: ChannelClosure?, failure: FailureClosure?) {
|
||||
create(.channelsCreate, name: channel, success: success, failure: failure)
|
||||
}
|
||||
|
||||
public func inviteToChannel(_ channel: String, user: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
invite(.channelsInvite, channel: channel, user: user, success: success, failure: failure)
|
||||
}
|
||||
|
||||
public func channelsJoin(_ name: String, validate: Bool, success: ChannelClosure?, failure: FailureClosure?) {
|
||||
join(.channelsJoin, name: name, validate: validate, success: success, failure: failure)
|
||||
}
|
||||
|
||||
public func channelsLeave(_ channel: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
leave(.channelsLeave, channel: channel, success: success, failure: failure)
|
||||
}
|
||||
|
||||
public func channelsArchive(_ channel: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
archive(.channelsArchive, channel: channel, success: success, failure: failure)
|
||||
}
|
||||
|
||||
public func channelsUnarchive(_ channel: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
unarchive(.channelsUnarchive, channel: channel, success: success, failure: failure)
|
||||
}
|
||||
@@ -256,8 +182,8 @@ extension WebAPI {
|
||||
// MARK: - Messaging
|
||||
extension WebAPI {
|
||||
public func deleteMessage(channel: String, ts: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channel, "ts": ts]
|
||||
networkInterface.request(.chatDelete, parameters: parameters, successClosure: { _ in
|
||||
let parameters: [String: Any] = ["channel": channel, "ts": ts]
|
||||
networkInterface.request(.chatDelete, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -281,7 +207,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"channel": channel,
|
||||
"text": text,
|
||||
"as_user": asUser,
|
||||
@@ -295,7 +220,7 @@ extension WebAPI {
|
||||
"attachments": encodeAttachments(attachments),
|
||||
"blocks": encodeBlocks(blocks)
|
||||
]
|
||||
networkInterface.request(.chatPostMessage, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.chatPostMessage, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?((ts: response["ts"] as? String, response["channel"] as? String))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -320,7 +245,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"channel": channel,
|
||||
"thread_ts": thread,
|
||||
"text": text,
|
||||
@@ -335,7 +259,7 @@ extension WebAPI {
|
||||
"icon_emoji": iconEmoji,
|
||||
"attachments": encodeAttachments(attachments)
|
||||
]
|
||||
networkInterface.request(.chatPostMessage, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.chatPostMessage, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?((ts: response["ts"] as? String, response["channel"] as? String))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -356,7 +280,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"channel": channel,
|
||||
"text": text,
|
||||
"user": user,
|
||||
@@ -367,7 +290,7 @@ extension WebAPI {
|
||||
"link_names": linkNames,
|
||||
"parse": parse?.rawValue,
|
||||
]
|
||||
networkInterface.request(.chatPostEphemeral, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.chatPostEphemeral, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?((ts: response["message_ts"] as? String, response["channel"] as? String))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -380,8 +303,8 @@ extension WebAPI {
|
||||
success: (((ts: String?, channel: String?)) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = ["token": token, "channel": channel, "text": text]
|
||||
networkInterface.request(.chatMeMessage, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any?] = ["channel": channel, "text": text]
|
||||
networkInterface.request(.chatMeMessage, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?((ts: response["ts"] as? String, response["channel"] as? String))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -399,7 +322,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"channel": channel,
|
||||
"ts": ts,
|
||||
"text": message,
|
||||
@@ -407,7 +329,7 @@ extension WebAPI {
|
||||
"link_names": linkNames,
|
||||
"attachments": encodeAttachments(attachments)
|
||||
]
|
||||
networkInterface.request(.chatUpdate, parameters: parameters, successClosure: { _ in
|
||||
networkInterface.request(.chatUpdate, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -418,8 +340,8 @@ extension WebAPI {
|
||||
// MARK: - Do Not Disturb
|
||||
extension WebAPI {
|
||||
public func dndInfo(user: String? = nil, success: ((_ status: DoNotDisturbStatus) -> Void)?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any?] = ["token": token, "user": user]
|
||||
networkInterface.request(.dndInfo, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any?] = ["user": user]
|
||||
networkInterface.request(.dndInfo, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(DoNotDisturbStatus(status: response))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -431,8 +353,8 @@ extension WebAPI {
|
||||
success: ((_ statuses: [String: DoNotDisturbStatus]) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = ["token": token, "users": users?.joined(separator: ",")]
|
||||
networkInterface.request(.dndTeamInfo, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any?] = ["users": users?.joined(separator: ",")]
|
||||
networkInterface.request(.dndTeamInfo, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
guard let usersDictionary = response["users"] as? [String: Any] else {
|
||||
success?([:])
|
||||
return
|
||||
@@ -447,7 +369,7 @@ extension WebAPI {
|
||||
// MARK: - Emoji
|
||||
extension WebAPI {
|
||||
public func emojiList(success: ((_ emojiList: [String: Any]?) -> Void)?, failure: FailureClosure?) {
|
||||
networkInterface.request(.emojiList, parameters: ["token": token], successClosure: {(response) in
|
||||
networkInterface.request(.emojiList, accessToken: token, parameters: [:], successClosure: {(response) in
|
||||
success?(response["emoji"] as? [String: Any])
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -458,8 +380,8 @@ extension WebAPI {
|
||||
// MARK: - Files
|
||||
extension WebAPI {
|
||||
public func deleteFile(fileID: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
let parameters = ["token": token, "file": fileID]
|
||||
networkInterface.request(.filesDelete, parameters: parameters, successClosure: { _ in
|
||||
let parameters = ["file": fileID]
|
||||
networkInterface.request(.filesDelete, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -473,8 +395,8 @@ extension WebAPI {
|
||||
success: FileClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "file": fileID, "count": count, "page": page]
|
||||
networkInterface.request(.filesInfo, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any] = ["file": fileID, "count": count, "page": page]
|
||||
networkInterface.request(.filesInfo, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
var file = File(file: response["file"] as? [String: Any])
|
||||
(response["comments"] as? [[String: Any]])?.forEach { comment in
|
||||
let comment = Comment(comment: comment)
|
||||
@@ -500,7 +422,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"filename": filename,
|
||||
"filetype": filetype,
|
||||
"title": title,
|
||||
@@ -508,7 +429,7 @@ extension WebAPI {
|
||||
"channels": channels?.joined(separator: ","),
|
||||
"thread_ts": ts
|
||||
]
|
||||
networkInterface.uploadRequest(data: file, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.uploadRequest(data: file, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(File(file: response["file"] as? [String: Any]))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -519,8 +440,8 @@ extension WebAPI {
|
||||
// MARK: - File Comments
|
||||
extension WebAPI {
|
||||
public func addFileComment(fileID: String, comment: String, success: CommentClosure?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any] = ["token": token, "file": fileID, "comment": comment]
|
||||
networkInterface.request(.filesCommentsAdd, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any] = ["file": fileID, "comment": comment]
|
||||
networkInterface.request(.filesCommentsAdd, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(Comment(comment: response["comment"] as? [String: Any]))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -528,8 +449,8 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
public func editFileComment(fileID: String, commentID: String, comment: String, success: CommentClosure?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any] = ["token": token, "file": fileID, "id": commentID, "comment": comment]
|
||||
networkInterface.request(.filesCommentsEdit, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any] = ["file": fileID, "id": commentID, "comment": comment]
|
||||
networkInterface.request(.filesCommentsEdit, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(Comment(comment: response["comment"] as? [String: Any]))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -537,8 +458,8 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
public func deleteFileComment(fileID: String, commentID: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any] = ["token": token, "file": fileID, "id": commentID]
|
||||
networkInterface.request(.filesCommentsDelete, parameters: parameters, successClosure: { _ in
|
||||
let parameters: [String: Any] = ["file": fileID, "id": commentID]
|
||||
networkInterface.request(.filesCommentsDelete, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -610,8 +531,8 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
public func openGroup(channel: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
let parameters = ["token": token, "channel": channel]
|
||||
networkInterface.request(.groupsOpen, parameters: parameters, successClosure: { _ in
|
||||
let parameters = ["channel": channel]
|
||||
networkInterface.request(.groupsOpen, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -689,16 +610,6 @@ extension WebAPI {
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func openIM(userID: String, success: ((_ imID: String?) -> Void)?, failure: FailureClosure?) {
|
||||
let parameters = ["token": token, "user": userID]
|
||||
networkInterface.request(.imOpen, parameters: parameters, successClosure: {(response) in
|
||||
let group = response["channel"] as? [String: Any]
|
||||
success?(group?["id"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MPIM
|
||||
@@ -757,8 +668,8 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
public func openMPIM(userIDs: [String], success: ((_ mpimID: String?) -> Void)?, failure: FailureClosure?) {
|
||||
let parameters = ["token": token, "users": userIDs.joined(separator: ",")]
|
||||
networkInterface.request(.mpimOpen, parameters: parameters, successClosure: {(response) in
|
||||
let parameters = ["users": userIDs.joined(separator: ",")]
|
||||
networkInterface.request(.mpimOpen, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
let group = response["group"] as? [String: Any]
|
||||
success?(group?["id"] as? String)
|
||||
}) {(error) in
|
||||
@@ -775,10 +686,9 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"channel": channel
|
||||
]
|
||||
networkInterface.request(.pinsList, parameters: parameters, successClosure: { response in
|
||||
networkInterface.request(.pinsList, accessToken: token, parameters: parameters, successClosure: { response in
|
||||
let items = response["items"] as? [[String: Any]]
|
||||
success?(items?.map({ Item(item: $0) }))
|
||||
}) {(error) in
|
||||
@@ -826,13 +736,12 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"channel": channel,
|
||||
"file": file,
|
||||
"file_comment": fileComment,
|
||||
"timestamp": timestamp
|
||||
]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: { _ in
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -915,14 +824,13 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"name": name,
|
||||
"file": file,
|
||||
"file_comment": fileComment,
|
||||
"channel": channel,
|
||||
"timestamp": timestamp
|
||||
]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: { _ in
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -962,14 +870,13 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"file": file,
|
||||
"file_comment": comment,
|
||||
"channel": channel,
|
||||
"timestamp": timestamp,
|
||||
"full": full
|
||||
]
|
||||
networkInterface.request(.reactionsGet, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.reactionsGet, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
guard let item = response[type.rawValue] as? [String: Any] else {
|
||||
reactions?([])
|
||||
return
|
||||
@@ -999,13 +906,12 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"user": user,
|
||||
"full": full,
|
||||
"count": count,
|
||||
"page": page
|
||||
]
|
||||
networkInterface.request(.reactionsList, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.reactionsList, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
let items = response["items"] as? [[String: Any]]
|
||||
success?(items?.map({ Item(item: $0) }))
|
||||
}) {(error) in
|
||||
@@ -1088,13 +994,12 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"file": file,
|
||||
"file_comment": fileComment,
|
||||
"channel": channel,
|
||||
"timestamp": timestamp
|
||||
]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: { _ in
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1105,7 +1010,7 @@ extension WebAPI {
|
||||
// MARK: - Team
|
||||
extension WebAPI {
|
||||
public func teamInfo(success: ((_ info: [String: Any]?) -> Void)?, failure: FailureClosure?) {
|
||||
networkInterface.request(.teamInfo, parameters: ["token": token], successClosure: {(response) in
|
||||
networkInterface.request(.teamInfo, accessToken: token, parameters: [:], successClosure: {(response) in
|
||||
success?(response["team"] as? [String: Any])
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1125,14 +1030,13 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any?] = [
|
||||
"token": token,
|
||||
"cursor": cursor,
|
||||
"exclude_archived": excludeArchived,
|
||||
"limit": limit,
|
||||
"types": types?.map({ $0.rawValue }).joined(separator: ","),
|
||||
"user": userID
|
||||
]
|
||||
networkInterface.request(.usersConversations, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.usersConversations, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
let channels: [Channel] = (response["channels"] as? [[String: Any]])?.map{Channel(channel: $0)} ?? []
|
||||
success?(channels, (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String)
|
||||
}) {(error) in
|
||||
@@ -1141,8 +1045,8 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
public func userPresence(user: String, success: ((_ presence: String?) -> Void)?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any] = ["token": token, "user": user]
|
||||
networkInterface.request(.usersGetPresence, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any] = ["user": user]
|
||||
networkInterface.request(.usersGetPresence, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(response["presence"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1150,8 +1054,8 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
public func userInfo(id: String, success: ((_ user: User) -> Void)?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any] = ["token": token, "user": id]
|
||||
networkInterface.request(.usersInfo, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any] = ["user": id]
|
||||
networkInterface.request(.usersInfo, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(User(user: response["user"] as? [String: Any]))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1163,7 +1067,7 @@ extension WebAPI {
|
||||
includePresence: Bool = false,
|
||||
success: ((_ userList: [[String: Any]]?, _ nextCursor: String?) -> Void)?,
|
||||
failure: FailureClosure?) {
|
||||
var parameters: [String: Any] = ["token": token, "presence": includePresence]
|
||||
var parameters: [String: Any] = ["presence": includePresence]
|
||||
if let cursor = cursor {
|
||||
parameters["cursor"] = cursor
|
||||
}
|
||||
@@ -1171,7 +1075,7 @@ extension WebAPI {
|
||||
parameters["limit"] = limit
|
||||
}
|
||||
|
||||
networkInterface.request(.usersList, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.usersList, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(response["members"] as? [[String: Any]], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1179,8 +1083,8 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
public func usersLookupByEmail(_ email: String, success: ((_ user: User) -> Void)?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any] = ["token": token, "email": email]
|
||||
networkInterface.request(.usersLookupByEmail, parameters: parameters, successClosure: { response in
|
||||
let parameters: [String: Any] = ["email": email]
|
||||
networkInterface.request(.usersLookupByEmail, accessToken: token, parameters: parameters, successClosure: { response in
|
||||
success?(User(user: response["user"] as? [String: Any]))
|
||||
}) { error in
|
||||
failure?(error)
|
||||
@@ -1196,28 +1100,19 @@ extension WebAPI {
|
||||
"phone": profile.phone,
|
||||
"status_text": profile.statusText,
|
||||
"status_emoji": profile.statusEmoji,
|
||||
"status_expiration": profile.statusExpiration,
|
||||
"status_expiration": profile.statusExpiration
|
||||
] as [String: Any?])
|
||||
.filter { $0.value != nil }
|
||||
.mapValues { $0! }
|
||||
|
||||
do {
|
||||
let data = try JSONSerialization.data(withJSONObject: profileValues)
|
||||
let json = String(data: data, encoding: .utf8)
|
||||
guard let encodedJSON = json?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
|
||||
throw SlackError.clientJSONError
|
||||
}
|
||||
var urlComponents = URLComponents(string: "https://slack.com/api/users.profile.set")
|
||||
urlComponents?.queryItems = [
|
||||
URLQueryItem(name: "token", value: token),
|
||||
URLQueryItem(name: "profile", value: encodedJSON)
|
||||
]
|
||||
|
||||
let data = try JSONSerialization.data(withJSONObject: ["profile": profileValues])
|
||||
let urlComponents = URLComponents(string: "https://slack.com/api/users.profile.set")
|
||||
guard let requestString = urlComponents?.url?.absoluteString else {
|
||||
throw SlackError.clientNetworkError
|
||||
}
|
||||
|
||||
networkInterface.customRequest(requestString, data: Data(), success: { _ in
|
||||
networkInterface.customRequest(requestString, token: token, data: data, success: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1228,7 +1123,7 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
public func setUserActive(success: SuccessClosure?, failure: FailureClosure?) {
|
||||
networkInterface.request(.usersSetActive, parameters: ["token": token], successClosure: { _ in
|
||||
networkInterface.request(.usersSetActive, accessToken: token, parameters: [:], successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1236,8 +1131,8 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
public func setUserPresence(presence: Presence, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any] = ["token": token, "presence": presence.rawValue]
|
||||
networkInterface.request(.usersSetPresence, parameters: parameters, successClosure: { _ in
|
||||
let parameters: [String: Any] = ["presence": presence.rawValue]
|
||||
networkInterface.request(.usersSetPresence, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1247,6 +1142,35 @@ extension WebAPI {
|
||||
|
||||
// MARK: - Conversations
|
||||
extension WebAPI {
|
||||
public func conversationsArchive(channel: String, success: (() -> Void)?, failure: FailureClosure?) {
|
||||
let parameters = ["channel": channel]
|
||||
networkInterface.request(.conversationsArchive, accessToken: token, parameters: parameters, successClosure: {_ in }) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func conversationsCreate(
|
||||
name: String,
|
||||
isPrivate: Bool = false,
|
||||
success: ((_ id: String?, _ name: String?, _ creator: String?) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters = [
|
||||
"name": name,
|
||||
"is_private": isPrivate
|
||||
] as [String : Any]
|
||||
networkInterface.request(.conversationsOpen, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
let group = response["channel"] as? [String: Any]
|
||||
success?(
|
||||
group?["id"] as? String,
|
||||
group?["name"] as? String,
|
||||
group?["creator"] as? String
|
||||
)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func conversationsList(
|
||||
excludeArchived: Bool = false,
|
||||
cursor: String? = nil,
|
||||
@@ -1255,7 +1179,7 @@ extension WebAPI {
|
||||
success: ((_ channels: [[String: Any]]?, _ nextCursor: String?) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
var parameters: [String: Any] = ["token": token, "exclude_archived": excludeArchived]
|
||||
var parameters: [String: Any] = ["exclude_archived": excludeArchived]
|
||||
if let cursor = cursor {
|
||||
parameters["cursor"] = cursor
|
||||
}
|
||||
@@ -1265,7 +1189,7 @@ extension WebAPI {
|
||||
if let types = types {
|
||||
parameters["types"] = types.map({ $0.rawValue }).joined(separator: ",")
|
||||
}
|
||||
networkInterface.request(.conversationsList, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.conversationsList, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(response["channels"] as? [[String: Any]], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1284,7 +1208,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
var parameters: [String: Any] = [
|
||||
"token": token,
|
||||
"channel": id,
|
||||
"ts": ts,
|
||||
"inclusive": inclusive,
|
||||
@@ -1295,7 +1218,7 @@ extension WebAPI {
|
||||
if let cursor = cursor {
|
||||
parameters["cursor"] = cursor
|
||||
}
|
||||
networkInterface.request(.conversationsReplies, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.conversationsReplies, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(response["messages"] as? [[String: Any]], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1310,7 +1233,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
var parameters: [String: Any] = [
|
||||
"token": token,
|
||||
"channel": id
|
||||
]
|
||||
if let cursor = cursor {
|
||||
@@ -1319,7 +1241,7 @@ extension WebAPI {
|
||||
if let limit = limit {
|
||||
parameters["limit"] = limit
|
||||
}
|
||||
networkInterface.request(.conversationsMembers, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.conversationsMembers, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(response["members"] as? [String], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1337,7 +1259,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
var parameters: [String: Any] = [
|
||||
"token": token,
|
||||
"channel": id,
|
||||
"inclusive": inclusive,
|
||||
"limit": limit,
|
||||
@@ -1347,12 +1268,28 @@ extension WebAPI {
|
||||
if let cursor = cursor {
|
||||
parameters["cursor"] = cursor
|
||||
}
|
||||
networkInterface.request(.conversationsHistory, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(.conversationsHistory, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(response["messages"] as? [[String: Any]], (response["response_metadata"] as? [String: Any])?["next_cursor"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func conversationsOpen(
|
||||
userIDs: [String],
|
||||
success: ((_ imID: String?) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters = [
|
||||
"users": userIDs.joined(separator: ",")
|
||||
]
|
||||
networkInterface.request(.conversationsOpen, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
let group = response["channel"] as? [String: Any]
|
||||
success?(group?["id"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Search
|
||||
@@ -1377,7 +1314,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = [
|
||||
"token": token,
|
||||
"query": query,
|
||||
"count": count,
|
||||
"highlight": highlight,
|
||||
@@ -1385,7 +1321,7 @@ extension WebAPI {
|
||||
"sort": sort.rawValue,
|
||||
"sort_dir": sortDir.rawValue,
|
||||
]
|
||||
networkInterface.request(.searchAll, parameters: parameters, successClosure: { (response) in
|
||||
networkInterface.request(.searchAll, accessToken: token, parameters: parameters, successClosure: { (response) in
|
||||
success?(
|
||||
(response["files"] as? [String : Any])?["matches"] as? [[String : Any]],
|
||||
(response["messages"] as? [String : Any])?["matches"] as? [[String : Any]]
|
||||
@@ -1406,7 +1342,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = [
|
||||
"token": token,
|
||||
"query": query,
|
||||
"count": count,
|
||||
"highlight": highlight,
|
||||
@@ -1414,7 +1349,7 @@ extension WebAPI {
|
||||
"sort": sort.rawValue,
|
||||
"sort_dir": sortDir.rawValue,
|
||||
]
|
||||
networkInterface.request(.searchFiles, parameters: parameters, successClosure: { (response) in
|
||||
networkInterface.request(.searchFiles, accessToken: token, parameters: parameters, successClosure: { (response) in
|
||||
success?(
|
||||
(response["files"] as? [String : Any])?["matches"] as? [[String : Any]]
|
||||
)
|
||||
@@ -1434,7 +1369,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = [
|
||||
"token": token,
|
||||
"query": query,
|
||||
"count": count,
|
||||
"highlight": highlight,
|
||||
@@ -1442,7 +1376,7 @@ extension WebAPI {
|
||||
"sort": sort.rawValue,
|
||||
"sort_dir": sortDir.rawValue,
|
||||
]
|
||||
networkInterface.request(.searchMessages, parameters: parameters, successClosure: { (response) in
|
||||
networkInterface.request(.searchMessages, accessToken: token, parameters: parameters, successClosure: { (response) in
|
||||
success?(
|
||||
(response["messages"] as? [String : Any])?["matches"] as? [[String : Any]]
|
||||
)
|
||||
@@ -1494,8 +1428,8 @@ extension WebAPI {
|
||||
}
|
||||
|
||||
fileprivate func close(_ endpoint: Endpoint, channelID: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channelID]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: { _ in
|
||||
let parameters: [String: Any] = ["channel": channelID]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1514,7 +1448,6 @@ extension WebAPI {
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = [
|
||||
"token": token,
|
||||
"channel": id,
|
||||
"latest": latest,
|
||||
"oldest": oldest,
|
||||
@@ -1522,7 +1455,7 @@ extension WebAPI {
|
||||
"count": count,
|
||||
"unreads": unreads
|
||||
]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(History(history: response))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1536,8 +1469,8 @@ extension WebAPI {
|
||||
success: ChannelClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": id]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any] = ["channel": id]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(Channel(channel: response[type.rawValue] as? [String: Any]))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1552,8 +1485,8 @@ extension WebAPI {
|
||||
success: ((_ channels: [[String: Any]]?) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "exclude_archived": excludeArchived, "exclude_members": excludeMembers]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any] = ["exclude_archived": excludeArchived, "exclude_members": excludeMembers]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(response[type.rawValue+"s"] as? [[String: Any]])
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1567,8 +1500,8 @@ extension WebAPI {
|
||||
success: ((_ ts: String) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channel, "ts": timestamp]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: { _ in
|
||||
let parameters: [String: Any] = ["channel": channel, "ts": timestamp]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(timestamp)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1583,8 +1516,8 @@ extension WebAPI {
|
||||
success: SuccessClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channel, type.rawValue: text]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: { _ in
|
||||
let parameters: [String: Any] = ["channel": channel, type.rawValue: text]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1597,8 +1530,8 @@ extension WebAPI {
|
||||
success: ChannelClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "name": name]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
|
||||
let parameters: [String: Any] = ["name": name]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
success?(Channel(channel: response["channel"] as? [String: Any]))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1612,8 +1545,8 @@ extension WebAPI {
|
||||
success: SuccessClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channel, "user": user]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: { _ in
|
||||
let parameters: [String: Any] = ["channel": channel, "user": user]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1627,8 +1560,8 @@ extension WebAPI {
|
||||
success: ChannelClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "name": name, "validate": validate]
|
||||
networkInterface.request(endpoint, parameters: parameters, successClosure: { response in
|
||||
let parameters: [String: Any] = ["name": name, "validate": validate]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters, successClosure: { response in
|
||||
success?(Channel(channel: response["channel"] as? [String: Any]))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1641,8 +1574,8 @@ extension WebAPI {
|
||||
success: SuccessClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channel]
|
||||
networkInterface.request(endpoint, parameters: parameters,successClosure: { _ in
|
||||
let parameters: [String: Any] = ["channel": channel]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1655,8 +1588,8 @@ extension WebAPI {
|
||||
success: SuccessClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channel]
|
||||
networkInterface.request(endpoint, parameters: parameters,successClosure: { _ in
|
||||
let parameters: [String: Any] = ["channel": channel]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1669,8 +1602,8 @@ extension WebAPI {
|
||||
success: SuccessClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channel]
|
||||
networkInterface.request(endpoint, parameters: parameters,successClosure: { _ in
|
||||
let parameters: [String: Any] = ["channel": channel]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1685,8 +1618,8 @@ extension WebAPI {
|
||||
success: ChannelClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channel, "name": name, "validate": validate ]
|
||||
networkInterface.request(endpoint, parameters: parameters,successClosure: { response in
|
||||
let parameters: [String: Any] = ["channel": channel, "name": name, "validate": validate ]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { response in
|
||||
success?(Channel(channel: response["channel"] as? [String: Any]))
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
@@ -1700,11 +1633,80 @@ extension WebAPI {
|
||||
success: SuccessClosure?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
let parameters: [String: Any] = ["token": token, "channel": channel, "user": user]
|
||||
networkInterface.request(endpoint, parameters: parameters,successClosure: { _ in
|
||||
let parameters: [String: Any] = ["channel": channel, "user": user]
|
||||
networkInterface.request(endpoint, accessToken: token, parameters: parameters,successClosure: { _ in
|
||||
success?(true)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Deprecated
|
||||
extension WebAPI {
|
||||
|
||||
// MARK: channels.*
|
||||
@available(*, deprecated, message: "Use conversationsArchive instead.")
|
||||
public func channelsArchive(_ channel: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
archive(.channelsArchive, channel: channel, success: success, failure: failure)
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use conversationsCreate instead.")
|
||||
public func createChannel(channel: String, success: ChannelClosure?, failure: FailureClosure?) {
|
||||
create(.channelsCreate, name: channel, success: success, failure: failure)
|
||||
}
|
||||
|
||||
@available(*, deprecated)
|
||||
public func channelInfo(id: String, success: ChannelClosure?, failure: FailureClosure?) {
|
||||
info(.channelsInfo, type:.channel, id: id, success: {(channel) in
|
||||
success?(channel)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated)
|
||||
public func inviteToChannel(_ channel: String, user: String, success: SuccessClosure?, failure: FailureClosure?) {
|
||||
invite(.channelsInvite, channel: channel, user: user, success: success, failure: failure)
|
||||
}
|
||||
|
||||
@available(*, deprecated)
|
||||
public func channelsJoin(_ name: String, validate: Bool, success: ChannelClosure?, failure: FailureClosure?) {
|
||||
join(.channelsJoin, name: name, validate: validate, success: success, failure: failure)
|
||||
}
|
||||
|
||||
@available(*, deprecated)
|
||||
public func channelsList(
|
||||
excludeArchived: Bool = false,
|
||||
excludeMembers: Bool = false,
|
||||
success: ((_ channels: [[String: Any]]?) -> Void)?,
|
||||
failure: FailureClosure?
|
||||
) {
|
||||
list(.channelsList, type:.channel, excludeArchived: excludeArchived, excludeMembers: excludeMembers, success: {(channels) in
|
||||
success?(channels)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated)
|
||||
public func markChannel(channel: String, timestamp: String, success: ((_ ts: String) -> Void)?, failure: FailureClosure?) {
|
||||
mark(.channelsMark, channel: channel, timestamp: timestamp, success: {(ts) in
|
||||
success?(ts)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: im.*
|
||||
@available(*, deprecated, message: "Use conversationsOpen instead.")
|
||||
public func openIM(userID: String, success: ((_ imID: String?) -> Void)?, failure: FailureClosure?) {
|
||||
let parameters = ["user": userID]
|
||||
networkInterface.request(.imOpen, accessToken: token, parameters: parameters, successClosure: {(response) in
|
||||
let group = response["channel"] as? [String: Any]
|
||||
success?(group?["id"] as? String)
|
||||
}) {(error) in
|
||||
failure?(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SlackKit"
|
||||
s.version = "4.6.0"
|
||||
s.version = "4.7.0"
|
||||
s.summary = "Write Slack apps in Swift"
|
||||
s.homepage = "https://github.com/pvzig/SlackKit"
|
||||
s.license = "MIT"
|
||||
@@ -8,7 +8,7 @@ Pod::Spec.new do |s|
|
||||
s.source = { :git => "https://github.com/pvzig/SlackKit.git", :tag => s.version.to_s }
|
||||
s.social_media_url = "https://twitter.com/pvzig"
|
||||
s.platforms = { :ios => '10.0', :osx => '10.11', :tvos => '10.0' }
|
||||
s.swift_version = '5.2.4'
|
||||
s.swift_version = '5.5.2'
|
||||
s.cocoapods_version = '>= 1.4.0'
|
||||
s.default_subspec = "SlackKit"
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
A20D15EC22DE158000044CFC /* BlockLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20D15E922DE158000044CFC /* BlockLayout.swift */; };
|
||||
AD39B504252DFF0B000CF632 /* Swifter.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD39B503252DFF0A000CF632 /* Swifter.xcframework */; };
|
||||
AD39B508252DFF1A000CF632 /* Starscream.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD39B507252DFF1A000CF632 /* Starscream.xcframework */; };
|
||||
DBA0851E258F83DB000F4381 /* attachment.json in Resources */ = {isa = PBXBuildFile; fileRef = DBA0851D258F83DB000F4381 /* attachment.json */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -312,6 +313,7 @@
|
||||
A20D15E922DE158000044CFC /* BlockLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockLayout.swift; sourceTree = "<group>"; };
|
||||
AD39B503252DFF0A000CF632 /* Swifter.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Swifter.xcframework; sourceTree = "<group>"; };
|
||||
AD39B507252DFF1A000CF632 /* Starscream.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Starscream.xcframework; sourceTree = "<group>"; };
|
||||
DBA0851D258F83DB000F4381 /* attachment.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = attachment.json; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -789,6 +791,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9EA45FB822C01290006A6D36 /* action.json */,
|
||||
DBA0851D258F83DB000F4381 /* attachment.json */,
|
||||
9EEC459722BE789600206AC3 /* attachmentfield.json */,
|
||||
26D4E6282220731800A67B67 /* channel.json */,
|
||||
26D4E6222220731700A67B67 /* conversation.json */,
|
||||
@@ -1135,6 +1138,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DBA0851E258F83DB000F4381 /* attachment.json in Resources */,
|
||||
2601B6CD2223038A00F197AB /* channel.json in Resources */,
|
||||
9EA45FB922C01290006A6D36 /* action.json in Resources */,
|
||||
2601B710222F766D00F197AB /* member_left_channel.json in Resources */,
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"fallback": "any fallback",
|
||||
"callback_id": "any callback_id",
|
||||
"attachment_type": "any attachment_type",
|
||||
"color": "any color",
|
||||
"pretext": "any pretext",
|
||||
"author_name": "any author_name",
|
||||
"author_link": "any author_link",
|
||||
"author_icon": "any author_icon",
|
||||
"title": "any title",
|
||||
"title_link": "any title_link",
|
||||
"text": "any text",
|
||||
"image_url": "any image_url",
|
||||
"thumb_url": "any thumb_url",
|
||||
"footer": "any footer",
|
||||
"footer_icon": "any footer_icon",
|
||||
"ts": 100,
|
||||
"fields": [
|
||||
{
|
||||
"title": "any title",
|
||||
"value": "any value",
|
||||
"short": true
|
||||
},
|
||||
{
|
||||
"title": "another title",
|
||||
"value": "another value",
|
||||
"short": false
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "any name",
|
||||
"text": "any text",
|
||||
"type": "any type",
|
||||
"value": "any value",
|
||||
"url": "any url",
|
||||
"style": "primary",
|
||||
"confirm":
|
||||
{
|
||||
"title": "any title",
|
||||
"text": "any text",
|
||||
"ok_text": "any ok text",
|
||||
"dismiss_text": "any dismiss text"
|
||||
},
|
||||
"options":
|
||||
[
|
||||
{
|
||||
"text": "any text 1",
|
||||
"value": "any value 1"
|
||||
},
|
||||
{
|
||||
"text": "any text 2",
|
||||
"value": "any value 2"
|
||||
}
|
||||
],
|
||||
"data_source": "channels"
|
||||
},
|
||||
{
|
||||
"name": "another name",
|
||||
"text": "another text",
|
||||
"type": "another type",
|
||||
"value": "another value",
|
||||
"url": "another url",
|
||||
"style": "default",
|
||||
"confirm":
|
||||
{
|
||||
"title": "another title",
|
||||
"text": "another text",
|
||||
"ok_text": "another ok text",
|
||||
"dismiss_text": "another dismiss text"
|
||||
},
|
||||
"options":
|
||||
[
|
||||
{
|
||||
"text": "another text 1",
|
||||
"value": "another value 1"
|
||||
}
|
||||
],
|
||||
"data_source": "channels"
|
||||
}
|
||||
],
|
||||
"mrkdwn_in": [
|
||||
"fallback", "fields", "title", "fallback", "fields"
|
||||
]
|
||||
}
|
||||
@@ -45,6 +45,7 @@ final class SKCoreTests: XCTestCase {
|
||||
static let usergroup = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/usergroup.json"))
|
||||
static let events = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/events.json"))
|
||||
static let action = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/action.json"))
|
||||
static let attachment = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/attachment.json"))
|
||||
static let attachmentfield = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/attachmentfield.json"))
|
||||
static let customprofilefield = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/customprofilefield.json"))
|
||||
static let donotdisturbstatus = try! Data(contentsOf: URL(fileURLWithPath: "\(rootPath)/donotdisturbstatus.json"))
|
||||
@@ -65,6 +66,7 @@ final class SKCoreTests: XCTestCase {
|
||||
("testUserGroup", testUserGroup),
|
||||
("testEvents", testEvents),
|
||||
("testActionCodable", testActionCodable),
|
||||
("testAttachmentCodable", testAttachmentCodable),
|
||||
("testAttachmentFieldCodable", testAttachmentFieldCodable),
|
||||
("testCustomProfileFieldCodable", testCustomProfileFieldCodable),
|
||||
("testDoNotDisturbStatusCodable", testDoNotDisturbStatusCodable),
|
||||
@@ -193,6 +195,57 @@ final class SKCoreTests: XCTestCase {
|
||||
XCTAssertEqual(actionBySerialization.dataSource, actionByDecoder!.dataSource)
|
||||
}
|
||||
|
||||
func testAttachmentCodable() {
|
||||
let data = JSONData.attachment
|
||||
let decoder = JSONDecoder()
|
||||
let attachmentByDecoder = try? decoder.decode(Attachment.self, from: data)
|
||||
XCTAssertNotNil(attachmentByDecoder)
|
||||
XCTAssertNotNil(attachmentByDecoder!.fallback)
|
||||
XCTAssertNotNil(attachmentByDecoder!.callbackID)
|
||||
XCTAssertNotNil(attachmentByDecoder!.type)
|
||||
XCTAssertNotNil(attachmentByDecoder!.color)
|
||||
XCTAssertNotNil(attachmentByDecoder!.pretext)
|
||||
XCTAssertNotNil(attachmentByDecoder!.authorName)
|
||||
XCTAssertNotNil(attachmentByDecoder!.authorLink)
|
||||
XCTAssertNotNil(attachmentByDecoder!.authorIcon)
|
||||
XCTAssertNotNil(attachmentByDecoder!.title)
|
||||
XCTAssertNotNil(attachmentByDecoder!.titleLink)
|
||||
XCTAssertNotNil(attachmentByDecoder!.text)
|
||||
XCTAssertNotNil(attachmentByDecoder!.fields)
|
||||
XCTAssertNotNil(attachmentByDecoder!.actions)
|
||||
XCTAssertNotNil(attachmentByDecoder!.imageURL)
|
||||
XCTAssertNotNil(attachmentByDecoder!.thumbURL)
|
||||
XCTAssertNotNil(attachmentByDecoder!.footer)
|
||||
XCTAssertNotNil(attachmentByDecoder!.footerIcon)
|
||||
XCTAssertNotNil(attachmentByDecoder!.ts)
|
||||
XCTAssertNotNil(attachmentByDecoder!.markdownEnabledFields)
|
||||
let encoder = JSONEncoder()
|
||||
let jsonData = try? encoder.encode(attachmentByDecoder!)
|
||||
XCTAssertNotNil(jsonData)
|
||||
let attachment = try? JSONSerialization.jsonObject(with: jsonData!, options: []) as? [String: Any]
|
||||
XCTAssertNotNil(attachment)
|
||||
let attachmentBySerialization = Attachment(attachment: attachment)
|
||||
XCTAssertEqual(attachmentBySerialization.fallback, attachmentByDecoder!.fallback)
|
||||
XCTAssertEqual(attachmentBySerialization.callbackID, attachmentByDecoder!.callbackID)
|
||||
XCTAssertEqual(attachmentBySerialization.type, attachmentByDecoder!.type)
|
||||
XCTAssertEqual(attachmentBySerialization.color, attachmentByDecoder!.color)
|
||||
XCTAssertEqual(attachmentBySerialization.pretext, attachmentByDecoder!.pretext)
|
||||
XCTAssertEqual(attachmentBySerialization.authorName, attachmentByDecoder!.authorName)
|
||||
XCTAssertEqual(attachmentBySerialization.authorLink, attachmentByDecoder!.authorLink)
|
||||
XCTAssertEqual(attachmentBySerialization.authorIcon, attachmentByDecoder!.authorIcon)
|
||||
XCTAssertEqual(attachmentBySerialization.title, attachmentByDecoder!.title)
|
||||
XCTAssertEqual(attachmentBySerialization.titleLink, attachmentByDecoder!.titleLink)
|
||||
XCTAssertEqual(attachmentBySerialization.text, attachmentByDecoder!.text)
|
||||
XCTAssertEqual(attachmentBySerialization.fields, attachmentByDecoder!.fields)
|
||||
XCTAssertEqual(attachmentBySerialization.actions, attachmentByDecoder!.actions)
|
||||
XCTAssertEqual(attachmentBySerialization.imageURL, attachmentByDecoder!.imageURL)
|
||||
XCTAssertEqual(attachmentBySerialization.thumbURL, attachmentByDecoder!.thumbURL)
|
||||
XCTAssertEqual(attachmentBySerialization.footer, attachmentByDecoder!.footer)
|
||||
XCTAssertEqual(attachmentBySerialization.footerIcon, attachmentByDecoder!.footerIcon)
|
||||
XCTAssertEqual(attachmentBySerialization.ts, attachmentByDecoder!.ts)
|
||||
XCTAssertEqual(attachmentBySerialization.markdownEnabledFields, attachmentByDecoder!.markdownEnabledFields)
|
||||
}
|
||||
|
||||
func testAttachmentFieldCodable() {
|
||||
let data = JSONData.attachmentfield
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
@@ -19,6 +19,7 @@ extension SKCoreTests {
|
||||
("testUser", testUser),
|
||||
("testUserGroup", testUserGroup),
|
||||
("testActionCodable", testActionCodable),
|
||||
("testAttachmentCodable", testAttachmentCodable),
|
||||
("testAttachmentFieldCodable", testAttachmentFieldCodable),
|
||||
("testCustomProfileFieldCodable", testCustomProfileFieldCodable),
|
||||
("testDoNotDisturbStatusCodable", testDoNotDisturbStatusCodable),
|
||||
|
||||
+22
-23
@@ -10,7 +10,7 @@ jobs:
|
||||
# Builds
|
||||
- job: macOS
|
||||
pool:
|
||||
vmImage: 'macOS-10.15'
|
||||
vmImage: 'macOS-11'
|
||||
steps:
|
||||
- task: Xcode@5
|
||||
inputs:
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
configuration: 'Release'
|
||||
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
|
||||
xcodeVersion: specifyPath
|
||||
xcodeDeveloperDir: '/Applications/Xcode_12.4.app/Contents/Developer'
|
||||
xcodeDeveloperDir: '/Applications/Xcode_13.0.app/Contents/Developer'
|
||||
- task: Xcode@5
|
||||
inputs:
|
||||
actions: 'test'
|
||||
@@ -29,10 +29,10 @@ jobs:
|
||||
configuration: 'Debug'
|
||||
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
|
||||
xcodeVersion: specifyPath
|
||||
xcodeDeveloperDir: '/Applications/Xcode_12.4.app/Contents/Developer'
|
||||
xcodeDeveloperDir: '/Applications/Xcode_13.0.app/Contents/Developer'
|
||||
- job: iOS
|
||||
pool:
|
||||
vmImage: 'macOS-10.15'
|
||||
vmImage: 'macOS-11'
|
||||
steps:
|
||||
- task: Xcode@5
|
||||
inputs:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
configuration: 'Release'
|
||||
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
|
||||
xcodeVersion: specifyPath
|
||||
xcodeDeveloperDir: '/Applications/Xcode_12.4.app/Contents/Developer'
|
||||
xcodeDeveloperDir: '/Applications/Xcode_13.0.app/Contents/Developer'
|
||||
- task: Xcode@5
|
||||
inputs:
|
||||
actions: 'test'
|
||||
@@ -51,15 +51,15 @@ jobs:
|
||||
configuration: 'Debug'
|
||||
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
|
||||
xcodeVersion: specifyPath
|
||||
xcodeDeveloperDir: '/Applications/Xcode_12.4.app/Contents/Developer'
|
||||
xcodeDeveloperDir: '/Applications/Xcode_13.0.app/Contents/Developer'
|
||||
destinationPlatformOption: 'custom'
|
||||
destinationPlatform: 'iOS'
|
||||
destinationTypeOption: 'simulators'
|
||||
destinationSimulators: 'iPhone 12 Pro,OS=14.4'
|
||||
destinationSimulators: 'iPhone 13 Pro,OS=15.0'
|
||||
|
||||
- job: tvOS
|
||||
pool:
|
||||
vmImage: 'macOS-10.15'
|
||||
vmImage: 'macOS-11'
|
||||
steps:
|
||||
- task: Xcode@5
|
||||
inputs:
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
configuration: 'Release'
|
||||
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
|
||||
xcodeVersion: specifyPath
|
||||
xcodeDeveloperDir: '/Applications/Xcode_12.4.app/Contents/Developer'
|
||||
xcodeDeveloperDir: '/Applications/Xcode_13.0.app/Contents/Developer'
|
||||
- task: Xcode@5
|
||||
inputs:
|
||||
actions: 'test'
|
||||
@@ -78,29 +78,29 @@ jobs:
|
||||
configuration: 'Debug'
|
||||
xcWorkspacePath: 'SlackKit.xcodeproj/project.xcworkspace'
|
||||
xcodeVersion: specifyPath
|
||||
xcodeDeveloperDir: '/Applications/Xcode_12.4.app/Contents/Developer'
|
||||
xcodeDeveloperDir: '/Applications/Xcode_13.0.app/Contents/Developer'
|
||||
destinationPlatformOption: 'custom'
|
||||
destinationPlatform: 'tvOS'
|
||||
destinationTypeOption: 'simulators'
|
||||
destinationSimulators: 'Apple TV 4K,OS=14.3'
|
||||
destinationSimulators: 'Apple TV 4K,OS=15.0'
|
||||
- job: Linux
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
vmImage: 'ubuntu-18.04'
|
||||
steps:
|
||||
- script: |
|
||||
# Install Swift dependencies
|
||||
sudo apt-get install clang libicu-dev
|
||||
# Install Swift 5.3.3
|
||||
curl https://swift.org/builds/swift-5.3.3-release/ubuntu1604/swift-5.3.3-RELEASE/swift-5.3.3-RELEASE-ubuntu16.04.tar.gz > $(Build.SourcesDirectory)/swift-5.3.3-RELEASE-ubuntu16.04.tar.gz
|
||||
tar xzf swift-5.3.3-RELEASE-ubuntu16.04.tar.gz -C $(Build.SourcesDirectory)
|
||||
# Install Swift 5.5.2
|
||||
curl https://download.swift.org/swift-5.5.2-release/ubuntu1804/swift-5.5.2-RELEASE/swift-5.5.2-RELEASE-ubuntu18.04.tar.gz > $(Build.SourcesDirectory)/swift-5.5.2-RELEASE-ubuntu18.04.tar.gz
|
||||
tar xzf swift-5.5.2-RELEASE-ubuntu18.04.tar.gz -C $(Build.SourcesDirectory)
|
||||
# Swift build
|
||||
$(Build.SourcesDirectory)/swift-5.3.3-RELEASE-ubuntu16.04/usr/bin/swift build
|
||||
$(Build.SourcesDirectory)/swift-5.5.2-RELEASE-ubuntu18.04/usr/bin/swift build
|
||||
# Swift test
|
||||
$(Build.SourcesDirectory)/swift-5.3.3-RELEASE-ubuntu16.04/usr/bin/swift test
|
||||
$(Build.SourcesDirectory)/swift-5.5.2-RELEASE-ubuntu18.04/usr/bin/swift test
|
||||
# Examples
|
||||
- job: 'Swift_Package_Manager'
|
||||
pool:
|
||||
vmImage: 'macOS-10.15'
|
||||
vmImage: 'macOS-11'
|
||||
steps:
|
||||
- script: |
|
||||
brew update
|
||||
@@ -109,15 +109,14 @@ jobs:
|
||||
swift build --package-path Examples/Leaderboard/
|
||||
- job: 'Carthage'
|
||||
pool:
|
||||
vmImage: 'macOS-10.15'
|
||||
vmImage: 'macOS-11'
|
||||
steps:
|
||||
- script: |
|
||||
carthage bootstrap --use-xcframeworks
|
||||
- script: carthage bootstrap --use-xcframeworks --platform macos
|
||||
workingDirectory: 'Examples/Leaderboard'
|
||||
|
||||
- job: 'CocoaPods'
|
||||
pool:
|
||||
vmImage: 'macOS-10.15'
|
||||
vmImage: 'macOS-11'
|
||||
steps:
|
||||
- task: CocoaPods@0
|
||||
inputs:
|
||||
@@ -130,6 +129,6 @@ jobs:
|
||||
configuration: 'Release'
|
||||
xcWorkspacePath: 'Examples/Leaderboard/Leaderboard.xcworkspace'
|
||||
xcodeVersion: specifyPath
|
||||
xcodeDeveloperDir: '/Applications/Xcode_12.4.app/Contents/Developer'
|
||||
xcodeDeveloperDir: '/Applications/Xcode_13.0.app/Contents/Developer'
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user