From 4f0620a1d624fee66928e3fc4f02d162c1ddbcf1 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 29 Jul 2018 14:33:21 -0400 Subject: [PATCH 001/104] get something compiling --- Package.resolved | 142 ++++++++--- Package.swift | 4 +- Sources/SwiftDiscord/DiscordClient.swift | 8 + .../SwiftDiscord/Gateway/DiscordEngine.swift | 17 +- .../Gateway/DiscordEngineSpec.swift | 110 +++------ .../SwiftDiscord/Gateway/DiscordGateway.swift | 10 +- .../Gateway/DiscordSharding.swift | 34 ++- .../Voice/DiscordVoiceEngine.swift | 223 +++++++++--------- .../Voice/DiscordVoiceEngineSpec.swift | 4 +- .../Voice/DiscordVoiceManager.swift | 19 +- 10 files changed, 319 insertions(+), 252 deletions(-) diff --git a/Package.resolved b/Package.resolved index a1c76345a..32e726bdb 100644 --- a/Package.resolved +++ b/Package.resolved @@ -2,21 +2,21 @@ "object": { "pins": [ { - "package": "Bits", - "repositoryURL": "https://github.com/vapor/bits.git", + "package": "SSCommonCrypto", + "repositoryURL": "https://github.com/daltoniam/common-crypto-spm", "state": { "branch": null, - "revision": "c32f5e6ae2007dccd21a92b7e33eba842dd80d2f", + "revision": "2eb3aff0fb57f92f5722fac5d6d20bf64669ca66", "version": "1.1.0" } }, { - "package": "SSCommonCrypto", - "repositoryURL": "https://github.com/daltoniam/common-crypto-spm", + "package": "Console", + "repositoryURL": "https://github.com/vapor/console.git", "state": { "branch": null, - "revision": "2eb3aff0fb57f92f5722fac5d6d20bf64669ca66", - "version": "1.1.0" + "revision": "5b9796d39f201b3dd06800437abd9d774a455e57", + "version": "3.0.2" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/vapor/core.git", "state": { "branch": null, - "revision": "f9f3a585ab0ea5764b46d7a36d9c0d9d508b9c63", - "version": "2.2.0" + "revision": "7f56a09995bf3c8df562be456bdcda405d9d0678", + "version": "3.4.1" } }, { @@ -42,53 +42,53 @@ "repositoryURL": "https://github.com/vapor/crypto.git", "state": { "branch": null, - "revision": "946edc6642d6825982a2f52a268a8ba9bd520a3d", - "version": "2.1.2" + "revision": "4b85405430df1892ee3aa1554bdb477e96cf46ad", + "version": "3.2.0" } }, { - "package": "CTLS", - "repositoryURL": "https://github.com/vapor/ctls.git", + "package": "DatabaseKit", + "repositoryURL": "https://github.com/vapor/database-kit.git", "state": { "branch": null, - "revision": "fba1297f4986a4dac8b02f7ac4e84a77fd492e4c", - "version": "1.1.3" + "revision": "7a01659316b9f033fa2150d5cd5e9d3c3e46c2e3", + "version": "1.3.0" } }, { - "package": "Debugging", - "repositoryURL": "https://github.com/vapor/debugging.git", + "package": "HTTP", + "repositoryURL": "https://github.com/vapor/http.git", "state": { "branch": null, - "revision": "fc5a27d6eb236141dc24e5f14eedaa2e035ae7b3", - "version": "1.1.1" + "revision": "8123ea00e9858b369cd168d0303d33e7d3804d19", + "version": "3.0.7" } }, { - "package": "Engine", - "repositoryURL": "https://github.com/vapor/engine", + "package": "Multipart", + "repositoryURL": "https://github.com/vapor/multipart.git", "state": { "branch": null, - "revision": "0ecc50fa8c7bc03ec9af78ca37e3b57f192bd258", - "version": "2.2.4" + "revision": "e57007c23a52b68e44ebdfc70cbe882a7c4f1ec3", + "version": "3.0.2" } }, { - "package": "Random", - "repositoryURL": "https://github.com/vapor/random.git", + "package": "Routing", + "repositoryURL": "https://github.com/vapor/routing.git", "state": { "branch": null, - "revision": "d7c4397d125caba795d14d956efacfe2a27a63d0", - "version": "1.2.0" + "revision": "3219e328491b0853b8554c5a694add344d2c6cfb", + "version": "3.0.1" } }, { - "package": "Sockets", - "repositoryURL": "https://github.com/vapor/sockets.git", + "package": "Service", + "repositoryURL": "https://github.com/vapor/service.git", "state": { "branch": null, - "revision": "79fc180cdcb451dbbb01b1156be36720adde80e0", - "version": "2.2.3" + "revision": "281a70b69783891900be31a9e70051b6fe19e146", + "version": "1.0.0" } }, { @@ -110,12 +110,84 @@ } }, { - "package": "TLS", - "repositoryURL": "https://github.com/vapor/tls.git", + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", + "state": { + "branch": null, + "revision": "695afc5205aaa49fca092b94b479ff71c43d9d3c", + "version": "1.8.0" + } + }, + { + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", + "state": { + "branch": null, + "revision": "6617eb0d3afcb12170594968df01ca63afb58ac5", + "version": "1.2.0" + } + }, + { + "package": "swift-nio-ssl-support", + "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", + "state": { + "branch": null, + "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", + "version": "1.0.0" + } + }, + { + "package": "swift-nio-zlib-support", + "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", + "state": { + "branch": null, + "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version": "1.0.0" + } + }, + { + "package": "TemplateKit", + "repositoryURL": "https://github.com/vapor/template-kit.git", + "state": { + "branch": null, + "revision": "43b57b5861d5181b906ac6411d28645e980bb638", + "version": "1.0.1" + } + }, + { + "package": "URLEncodedForm", + "repositoryURL": "https://github.com/vapor/url-encoded-form.git", + "state": { + "branch": null, + "revision": "cbfe7ef6301557d3f2d0807a98165232ae06e1c6", + "version": "1.0.4" + } + }, + { + "package": "Validation", + "repositoryURL": "https://github.com/vapor/validation.git", + "state": { + "branch": null, + "revision": "ab6c5a352d97c8687b91ed4963aef8e7cfe0795b", + "version": "2.0.0" + } + }, + { + "package": "Vapor", + "repositoryURL": "https://github.com/vapor/vapor", + "state": { + "branch": null, + "revision": "54632a6c1e7ecd9923c0d00b612de936de1c4745", + "version": "3.0.8" + } + }, + { + "package": "WebSocket", + "repositoryURL": "https://github.com/vapor/websocket.git", "state": { "branch": null, - "revision": "02a47309249e69358aa3c28b5853897585d7a750", - "version": "2.1.2" + "revision": "141cb4d3814dc8062cb0b2f43e72801b5dfcf272", + "version": "1.0.1" } }, { diff --git a/Package.swift b/Package.swift index a39195a2d..a86503b15 100644 --- a/Package.swift +++ b/Package.swift @@ -22,10 +22,10 @@ import PackageDescription var deps: [Package.Dependency] = [ .package(url: "https://github.com/nuclearace/copus", .upToNextMinor(from: "2.0.0")), .package(url: "https://github.com/nuclearace/Sodium", .upToNextMinor(from: "2.0.0")), - .package(url: "https://github.com/vapor/engine", .upToNextMinor(from: "2.2.0")), + .package(url: "https://github.com/vapor/websocket", .upToNextMinor(from: "1.0.0")), ] -var targetDeps: [Target.Dependency] = ["DiscordOpus", "WebSockets"] +var targetDeps: [Target.Dependency] = ["DiscordOpus", "WebSocket"] #if !os(Linux) deps += [.package(url: "https://github.com/daltoniam/Starscream", .upToNextMinor(from: "3.0.0")),] diff --git a/Sources/SwiftDiscord/DiscordClient.swift b/Sources/SwiftDiscord/DiscordClient.swift index 156d79e21..b9bc1a605 100644 --- a/Sources/SwiftDiscord/DiscordClient.swift +++ b/Sources/SwiftDiscord/DiscordClient.swift @@ -17,6 +17,7 @@ import Foundation import Dispatch +import NIO /// /// The base class for SwiftDiscord. Most interaction with Discord will be done through this class. @@ -43,6 +44,9 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// The rate limiter for this client. public var rateLimiter: DiscordRateLimiterSpec! + /// The run loops. + public let runloops = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) + /// The Discord JWT token. public let token: DiscordToken @@ -136,6 +140,10 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco rateLimiter = rateLimiter ?? DiscordRateLimiter(callbackQueue: handleQueue, failFast: false) } + deinit { + try! runloops.syncShutdownGracefully() + } + // MARK: Methods /// diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift index 95cf3eceb..246584d0a 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift @@ -16,11 +16,12 @@ // DEALINGS IN THE SOFTWARE. import Foundation -#if !os(Linux) -import Starscream -#else -import WebSockets -#endif +//#if !os(Linux) +//import Starscream +//#else +import NIO +import WebSocket +//#endif import Dispatch #if os(macOS) @@ -86,6 +87,9 @@ open class DiscordEngine : DiscordEngineSpec { /// The total number of shards. public let numShards: Int + /// The run loop for this shard. + public let runloop: EventLoop + /// The shard number of this engine. public let shardNum: Int @@ -135,10 +139,11 @@ open class DiscordEngine : DiscordEngineSpec { /// /// - parameter delegate: The DiscordClientSpec this engine should be associated with. /// - public required init(delegate: DiscordShardDelegate, shardNum: Int = 0, numShards: Int = 1) { + public required init(delegate: DiscordShardDelegate, shardNum: Int = 0, numShards: Int = 1, onLoop: EventLoop) { self.delegate = delegate self.shardNum = shardNum self.numShards = numShards + self.runloop = onLoop } // MARK: Methods diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index a9847054a..aaa74b48a 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -17,13 +17,9 @@ import Dispatch import Foundation -#if !os(Linux) -import Starscream -#else -import WebSockets -import Sockets -import TLS -#endif +import NIO +import HTTP +import WebSocket /// Declares that a type will be an Engine for the Discord Gateway. public protocol DiscordEngineSpec : DiscordShard { @@ -37,7 +33,7 @@ public protocol DiscordEngineSpec : DiscordShard { } /// Declares that a type will be capable of communicating with Discord's WebSockets -public protocol DiscordWebSocketable : class { +public protocol DiscordWebSocketable : AnyObject { /// MARK: Properties /// The url to connect to. @@ -85,37 +81,10 @@ public protocol DiscordWebSocketable : class { func handleClose(reason: Error?) } -public extension DiscordWebSocketable where Self: DiscordGatewayable { +public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRunLoopable { /// Default implementation. public func attachWebSocketHandlers() { - #if !os(Linux) - websocket?.onConnect = {[weak self] in - guard let this = self else { return } - - DefaultDiscordLogger.Logger.log("WebSocket Connected, \(this.description)", type: "DiscordWebSocketable") - - this.connectUUID = UUID() - - this.startHandshake() - } - - websocket?.onDisconnect = {[weak self] err in - guard let this = self else { return } - - DefaultDiscordLogger.Logger.log("WebSocket disconnected \(String(describing: err)), \(this.description)", type: "DiscordWebSocketable") - - this.handleClose(reason: err) - } - - websocket?.onText = {[weak self] string in - guard let this = self else { return } - - DefaultDiscordLogger.Logger.debug("\(this.description) Got text: \(string)", type: "DiscordWebSocketable") - - this.parseGatewayMessage(string) - } - #else - websocket?.onText = {[weak self] ws, text in + websocket?.onText {[weak self] ws, text in guard let this = self else { return } DefaultDiscordLogger.Logger.debug("\(this.description), Got text: \(text)", type: "DiscordWebSocketable") @@ -123,15 +92,13 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable { this.parseGatewayMessage(text) } - websocket?.onClose = {[weak self] _, _, reason, clean in + websocket?.onClose.do {[weak self] _ in guard let this = self else { return } - DefaultDiscordLogger.Logger.log("WebSocket closed, \(this.description); clean: \(clean); reason: \(reason ?? "")", - type: "DiscordWebSocketable") + DefaultDiscordLogger.Logger.log("WebSocket closed, \(this.description);", type: "DiscordWebSocketable") this.handleClose(reason: nil) - } - #endif + }.catch {_ in } } /// @@ -141,32 +108,31 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable { DefaultDiscordLogger.Logger.log("Connecting to \(connectURL), \(description)", type: "DiscordWebSocketable") DefaultDiscordLogger.Logger.log("Attaching WebSocket, shard: \(description)", type: "DiscordWebSocketable") - #if !os(Linux) - websocket = WebSocket(url: URL(string: connectURL)!) - websocket?.callbackQueue = parseQueue - - attachWebSocketHandlers() - websocket?.connect() - #else let url = URL(string: connectURL)! - do { - let socket = try TCPInternetSocket(scheme: "https", hostname: url.host ?? "gateway.discord.gg", - port: Port(url.port ?? 443)) - let stream = try TLS.InternetSocket(socket, TLS.Context(.client)) - try WebSocket.background(to: connectURL, using: stream) {[weak self] ws in - guard let this = self else { return } - DefaultDiscordLogger.Logger.log("Websocket connected, shard: \(this.description)", type: "DiscordWebSocketable") - - this.websocket = ws - this.connectUUID = UUID() - - this.attachWebSocketHandlers() - this.startHandshake() - } - } catch { - DefaultDiscordLogger.Logger.error("\(error)", type: "DiscordWebSocketable") + let path = url.path.isEmpty ? "/" : url.path + + let future = HTTPClient.webSocket(scheme: .wss, + hostname: url.host!, + port: url.port, + path: path, + on: runloop + ) + + let doneFuture = runloop.newSucceededFuture(result: ()) + + _ = future.then {[weak self] ws -> EventLoopFuture<()> in + guard let this = self else { return doneFuture } + + DefaultDiscordLogger.Logger.log("Websocket connected, shard: \(this.description)", type: "DiscordWebSocketable") + + this.websocket = ws + this.connectUUID = UUID() + + this.attachWebSocketHandlers() + this.startHandshake() + + return doneFuture } - #endif } internal func closeWebSockets(fast: Bool = false) { @@ -178,17 +144,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable { return } - #if !os(Linux) - websocket?.disconnect() - #else - do { - try websocket?.close() - } catch { - DefaultDiscordLogger.Logger.log("Error in closing: \(error)", type: "DiscordWebSocketable") - - handleClose(reason: nil) - } - #endif + websocket?.close() } /// diff --git a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift index a6ac78f65..9d1d9f26f 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift @@ -88,7 +88,7 @@ public protocol DiscordGatewayable : DiscordEngineHeartbeatable { func startHandshake() } -public extension DiscordGatewayable where Self: DiscordWebSocketable { +public extension DiscordGatewayable where Self: DiscordWebSocketable & DiscordRunLoopable { /// Default Implementation. func sendPayload(_ payload: DiscordGatewayPayload) { guard let payloadString = payload.createPayloadString() else { @@ -99,11 +99,9 @@ public extension DiscordGatewayable where Self: DiscordWebSocketable { DefaultDiscordLogger.Logger.debug("Sending ws: \(payloadString)", type: description) -#if !os(Linux) - websocket?.write(string: payloadString) -#else - try? websocket?.send(payloadString) -#endif + runloop.execute { + self.websocket?.send(payloadString) + } } } diff --git a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift index d8cb9470c..0467a52ce 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift @@ -17,6 +17,7 @@ import Dispatch import Foundation +import NIO /// Struct that represents shard information. /// Used when a client is doing manual sharding. @@ -49,9 +50,23 @@ public struct DiscordShardInformation { } } +/// Marks that a type will contain a runloop. +public protocol DiscordRunLoopable { + /// The run loop for this entity. + var runloop: EventLoop { get } +} + +/// Declares that a type will be manager of a run loop. +public protocol DiscordEventLoopGroupManager { + // MARK: Properties + + /// The run loops. + var runloops: MultiThreadedEventLoopGroup { get } +} + /// Protocol that represents a sharded gateway connection. This is the top-level protocol for `DiscordEngineSpec` and /// `DiscordEngine` -public protocol DiscordShard : DiscordWebSocketable, DiscordGatewayable { +public protocol DiscordShard : DiscordWebSocketable, DiscordGatewayable, DiscordRunLoopable { // MARK: Properties /// Whether this shard is connected to the gateway @@ -73,11 +88,11 @@ public protocol DiscordShard : DiscordWebSocketable, DiscordGatewayable { /// /// - parameter client: The client this engine should be associated with. /// - init(delegate: DiscordShardDelegate, shardNum: Int, numShards: Int) + init(delegate: DiscordShardDelegate, shardNum: Int, numShards: Int, onLoop: EventLoop) } /// Declares that a type will be a shard's delegate. -public protocol DiscordShardDelegate : class, DiscordTokenBearer { +public protocol DiscordShardDelegate : AnyObject, DiscordTokenBearer { /// /// Used by shards to signal that they have connected. /// @@ -114,7 +129,7 @@ public protocol DiscordShardDelegate : class, DiscordTokenBearer { } /// The delegate for a `DiscordShardManager`. -public protocol DiscordShardManagerDelegate : class, DiscordTokenBearer { +public protocol DiscordShardManagerDelegate : AnyObject, DiscordEventLoopGroupManager, DiscordTokenBearer { // MARK: Methods /// @@ -196,7 +211,7 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { let shards = get(self.shards) for (i, shard) in shards.enumerated() { - let deadline = DispatchTime(secondsFromNow: Double(5 * i)) + let deadline = DispatchTime.now() + Double(5 * i) DispatchQueue.global().asyncAfter(deadline: deadline) { [weak self, weak shard] in guard let this = self, this.get(!this.closed) else { return } shard?.connect() @@ -213,8 +228,8 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { /// - returns: A new `DiscordShard` /// open func createShardWithDelegate(_ delegate: DiscordShardManagerDelegate, withShardNum shardNum: Int, - totalShards: Int) -> DiscordShard { - return DiscordEngine(delegate: self, shardNum: shardNum, numShards: totalShards) + totalShards: Int, onloop: EventLoop) -> DiscordShard { + return DiscordEngine(delegate: self, shardNum: shardNum, numShards: totalShards, onLoop: onloop) } /// @@ -249,7 +264,10 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { protected { for shardNum in info.shardRange { - shards.append(createShardWithDelegate(delegate, withShardNum: shardNum, totalShards: info.totalShards)) + shards.append(createShardWithDelegate(delegate, + withShardNum: shardNum, + totalShards: info.totalShards, + onloop: delegate.runloops.next())) } } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift index 24b135a01..2f5817510 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift @@ -17,12 +17,12 @@ import Foundation import Dispatch -#if !os(Linux) -import Starscream -#else -import WebSockets -#endif -import Sockets +//#if !os(Linux) +//import Starscream +//#else +import WebSocket +//#endif +//import Sockets import Sodium /// @@ -52,6 +52,9 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// The parse queue. public let parseQueue = DispatchQueue(label: "discordVoiceEngine.parseQueue") + /// The run loop for this shard. + public let runloop: EventLoop + /// The voice url public var connectURL: String { return "wss://\(voiceServerInformation.endpoint.components(separatedBy: ":")[0])?v=3" @@ -106,8 +109,8 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// Our SSRC public private(set) var ssrc: UInt32 = 0 - /// The UDP socket that is used to send/receive voice data - public private(set) var udpSocket: UDPInternetSocket? +// /// The UDP socket that is used to send/receive voice data +// public private(set) var udpSocket: UDPInternetSocket? /// Our UDP port public private(set) var udpPort = -1 @@ -152,12 +155,14 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// - parameter secret: The secret from a previous engine. /// public init(delegate: DiscordVoiceEngineDelegate, + onLoop: EventLoop, config: DiscordVoiceEngineConfiguration, voiceServerInformation: DiscordVoiceServerInformation, voiceState: DiscordVoiceState, source: DiscordVoiceDataSource?, secret: [UInt8]?) { self.voiceDelegate = delegate + self.runloop = onLoop self.config = config _ = sodium_init() @@ -182,17 +187,17 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { // MARK: Methods private func closeOutEngine() { - guard !closed else { return } - - do { - try udpSocket?.close() - } catch { - self.error(message: "Error trying to close voice engine udp socket") - } - - closed = true - connected = false - sendTimer.cancel() +// guard !closed else { return } +// +// do { +// try udpSocket?.close() +// } catch { +// self.error(message: "Error trying to close voice engine udp socket") +// } +// +// closed = true +// connected = false +// sendTimer.cancel() } private func configureTimer() { @@ -287,24 +292,24 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { // https://discordapp.com/developers/docs/topics/voice-connections#ip-discovery private func findIP() { - udpQueueWrite.async { - guard let udpSocket = self.udpSocket else { return } - - // print("Finding IP") - let discoveryData = [UInt8](repeating: 0x00, count: 70) - - do { - try udpSocket.sendto(data: discoveryData) - - let (data, _) = try udpSocket.recvfrom(maxBytes: 70) - let (ip, port) = try self.extractIPAndPort(from: data) - - self.selectProtocol(with: ip, on: port) - } catch { - self.error(message: "Something went wrong extracting the ip and port \(error)") - self.disconnect() - } - } +// udpQueueWrite.async { +// guard let udpSocket = self.udpSocket else { return } +// +// // print("Finding IP") +// let discoveryData = [UInt8](repeating: 0x00, count: 70) +// +// do { +// try udpSocket.sendto(data: discoveryData) +// +// let (data, _) = try udpSocket.recvfrom(maxBytes: 70) +// let (ip, port) = try self.extractIPAndPort(from: data) +// +// self.selectProtocol(with: ip, on: port) +// } catch { +// self.error(message: "Something went wrong extracting the ip and port \(error)") +// self.disconnect() +// } +// } } private func getNewDataSource() { @@ -485,39 +490,39 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { private func readSocket() { // TODO refactor to be non-blocking - udpQueueRead.async {[weak self] in - guard let socket = self?.udpSocket, self?.connected ?? false else { return } - - do { - let (data, _) = try socket.recvfrom(maxBytes: 4096) - - DefaultDiscordLogger.Logger.debug("Received data \(data)", type: "DiscordVoiceEngine") - - guard let this = self else { return } - - let packet = DiscordOpusVoiceData(voicePacket: try this.decryptVoiceData(data)) - - if this.config.decodeVoice { - this.voiceDelegate?.voiceEngine(this, - didReceiveRawVoiceData: try this.decoderSession.decode(packet)) - } else { - this.voiceDelegate?.voiceEngine(this, didReceiveOpusVoiceData: packet) - } - } catch DiscordVoiceError.initialPacket { - DefaultDiscordLogger.Logger.debug("Got initial packet", type: DiscordVoiceEngine.logType) - } catch DiscordVoiceError.decodeFail { - DefaultDiscordLogger.Logger.debug("Failed to decode a packet", type: DiscordVoiceEngine.logType) - } catch EngineError.decryptionError { - self?.error(message: "Error decrypting voice packet") - } catch let err { - self?.error(message: "Error reading voice data from udp socket \(err)") - self?.disconnect() - - return - } - - self?.readSocket() - } +// udpQueueRead.async {[weak self] in +// guard let socket = self?.udpSocket, self?.connected ?? false else { return } +// +// do { +// let (data, _) = try socket.recvfrom(maxBytes: 4096) +// +// DefaultDiscordLogger.Logger.debug("Received data \(data)", type: "DiscordVoiceEngine") +// +// guard let this = self else { return } +// +// let packet = DiscordOpusVoiceData(voicePacket: try this.decryptVoiceData(data)) +// +// if this.config.decodeVoice { +// this.voiceDelegate?.voiceEngine(this, +// didReceiveRawVoiceData: try this.decoderSession.decode(packet)) +// } else { +// this.voiceDelegate?.voiceEngine(this, didReceiveOpusVoiceData: packet) +// } +// } catch DiscordVoiceError.initialPacket { +// DefaultDiscordLogger.Logger.debug("Got initial packet", type: DiscordVoiceEngine.logType) +// } catch DiscordVoiceError.decodeFail { +// DefaultDiscordLogger.Logger.debug("Failed to decode a packet", type: DiscordVoiceEngine.logType) +// } catch EngineError.decryptionError { +// self?.error(message: "Error decrypting voice packet") +// } catch let err { +// self?.error(message: "Error reading voice data from udp socket \(err)") +// self?.disconnect() +// +// return +// } +// +// self?.readSocket() +// } } /// @@ -600,28 +605,28 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// - parameter data: An Opus encoded packet. /// private func sendVoiceData(_ data: [UInt8]) { - guard let udpSocket = self.udpSocket, let frameSize = source?.frameSize, secret != nil else { return } - - if !speaking { - sendSpeaking(true) - } - - DefaultDiscordLogger.Logger.debug("Should send voice data: \(data.count) bytes", - type: DiscordVoiceEngine.logType) - - do { - try udpSocket.sendto(data: createVoicePacket(data)) - } catch EngineError.encryptionError { - error(message: "Error encrypting packet") - } catch let err { - error(message: "Failed sending voice packet \(err)") - disconnect() - - return - } - - sequenceNum = sequenceNum &+ 1 - timestamp = timestamp &+ UInt32(frameSize) +// guard let udpSocket = self.udpSocket, let frameSize = source?.frameSize, secret != nil else { return } +// +// if !speaking { +// sendSpeaking(true) +// } +// +// DefaultDiscordLogger.Logger.debug("Should send voice data: \(data.count) bytes", +// type: DiscordVoiceEngine.logType) +// +// do { +// try udpSocket.sendto(data: createVoicePacket(data)) +// } catch EngineError.encryptionError { +// error(message: "Error encrypting packet") +// } catch let err { +// error(message: "Failed sending voice packet \(err)") +// disconnect() +// +// return +// } +// +// sequenceNum = sequenceNum &+ 1 +// timestamp = timestamp &+ UInt32(frameSize) } #if !os(iOS) @@ -682,22 +687,22 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { } private func startUDP() { - guard udpPort != -1 else { return } - - let base = voiceServerInformation.endpoint.components(separatedBy: ":")[0] - let udpEndpoint = InternetAddress(hostname: base, port: UInt16(udpPort)) - - DefaultDiscordLogger.Logger.debug("Starting voice UDP connection", type: DiscordVoiceEngine.logType) - - guard let client = try? UDPInternetSocket(address: udpEndpoint) else { - disconnect() - - return - } - - udpSocket = client - - // Begin async UDP setup - findIP() +// guard udpPort != -1 else { return } +// +// let base = voiceServerInformation.endpoint.components(separatedBy: ":")[0] +// let udpEndpoint = InternetAddress(hostname: base, port: UInt16(udpPort)) +// +// DefaultDiscordLogger.Logger.debug("Starting voice UDP connection", type: DiscordVoiceEngine.logType) +// +// guard let client = try? UDPInternetSocket(address: udpEndpoint) else { +// disconnect() +// +// return +// } +// +// udpSocket = client +// +// // Begin async UDP setup +// findIP() } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngineSpec.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngineSpec.swift index 2b285f186..b4d9114be 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngineSpec.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngineSpec.swift @@ -19,7 +19,7 @@ import COPUS import Foundation /// Declares that a type will be a voice engine. -public protocol DiscordVoiceEngineSpec : DiscordWebSocketable, DiscordGatewayable { +public protocol DiscordVoiceEngineSpec : DiscordWebSocketable, DiscordGatewayable, DiscordRunLoopable { // MARK: Properties /// The encoder for this engine. The encoder is responsible for turning raw audio data into OPUS encoded data. @@ -60,7 +60,7 @@ public protocol DiscordVoiceEngineSpec : DiscordWebSocketable, DiscordGatewayabl } /// Declares that a type will be a client for a voice engine. -public protocol DiscordVoiceEngineDelegate : class { +public protocol DiscordVoiceEngineDelegate : AnyObject { // MARK: Methods /// diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift index d72afa4bb..6318f183c 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift @@ -19,7 +19,7 @@ import Dispatch import Foundation /// A delegate for a VoiceManager. -public protocol DiscordVoiceManagerDelegate : class, DiscordTokenBearer { +public protocol DiscordVoiceManagerDelegate : AnyObject, DiscordTokenBearer, DiscordEventLoopGroupManager { // MARK: Methods /// @@ -154,6 +154,8 @@ open class DiscordVoiceManager : DiscordVoiceEngineDelegate, Lockable { /// Tries to create a voice engine for a guild, and connect. /// **Not thread safe.** private func _startVoiceConnection(_ guildId: GuildID) { + guard let delegate = delegate else { return } + // We need both to start the connection guard let voiceState = voiceStates[guildId], let serverInfo = voiceServerInformations[guildId] else { return @@ -161,12 +163,15 @@ open class DiscordVoiceManager : DiscordVoiceEngineDelegate, Lockable { // Reuse a previous engine's encoder if possible let previousEngine = voiceEngines[guildId] - voiceEngines[guildId] = DiscordVoiceEngine(delegate: self, - config: engineConfiguration, - voiceServerInformation: serverInfo, - voiceState: voiceState, - source: previousEngine?.source, - secret: previousEngine?.secret) + voiceEngines[guildId] = DiscordVoiceEngine( + delegate: self, + onLoop: delegate.runloops.next(), + config: engineConfiguration, + voiceServerInformation: serverInfo, + voiceState: voiceState, + source: previousEngine?.source, + secret: previousEngine?.secret + ) DefaultDiscordLogger.Logger.log("Connecting voice engine", type: logType) From 1e019bae876c2bab060d7e91c0e75cf2b5788fbb Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sat, 4 Aug 2018 11:57:30 -0400 Subject: [PATCH 002/104] catch errors trying to connect --- Package.resolved | 87 +------------------ .../Gateway/DiscordEngineSpec.swift | 6 ++ 2 files changed, 9 insertions(+), 84 deletions(-) diff --git a/Package.resolved b/Package.resolved index 32e726bdb..b23cd59f3 100644 --- a/Package.resolved +++ b/Package.resolved @@ -10,15 +10,6 @@ "version": "1.1.0" } }, - { - "package": "Console", - "repositoryURL": "https://github.com/vapor/console.git", - "state": { - "branch": null, - "revision": "5b9796d39f201b3dd06800437abd9d774a455e57", - "version": "3.0.2" - } - }, { "package": "COPUS", "repositoryURL": "https://github.com/nuclearace/copus", @@ -46,15 +37,6 @@ "version": "3.2.0" } }, - { - "package": "DatabaseKit", - "repositoryURL": "https://github.com/vapor/database-kit.git", - "state": { - "branch": null, - "revision": "7a01659316b9f033fa2150d5cd5e9d3c3e46c2e3", - "version": "1.3.0" - } - }, { "package": "HTTP", "repositoryURL": "https://github.com/vapor/http.git", @@ -64,33 +46,6 @@ "version": "3.0.7" } }, - { - "package": "Multipart", - "repositoryURL": "https://github.com/vapor/multipart.git", - "state": { - "branch": null, - "revision": "e57007c23a52b68e44ebdfc70cbe882a7c4f1ec3", - "version": "3.0.2" - } - }, - { - "package": "Routing", - "repositoryURL": "https://github.com/vapor/routing.git", - "state": { - "branch": null, - "revision": "3219e328491b0853b8554c5a694add344d2c6cfb", - "version": "3.0.1" - } - }, - { - "package": "Service", - "repositoryURL": "https://github.com/vapor/service.git", - "state": { - "branch": null, - "revision": "281a70b69783891900be31a9e70051b6fe19e146", - "version": "1.0.0" - } - }, { "package": "Sodium", "repositoryURL": "https://github.com/nuclearace/Sodium", @@ -114,8 +69,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "695afc5205aaa49fca092b94b479ff71c43d9d3c", - "version": "1.8.0" + "revision": "c6acf77fc9a310c0ebf8d5f73f285d1639801d2f", + "version": "1.9.0" } }, { @@ -145,45 +100,9 @@ "version": "1.0.0" } }, - { - "package": "TemplateKit", - "repositoryURL": "https://github.com/vapor/template-kit.git", - "state": { - "branch": null, - "revision": "43b57b5861d5181b906ac6411d28645e980bb638", - "version": "1.0.1" - } - }, - { - "package": "URLEncodedForm", - "repositoryURL": "https://github.com/vapor/url-encoded-form.git", - "state": { - "branch": null, - "revision": "cbfe7ef6301557d3f2d0807a98165232ae06e1c6", - "version": "1.0.4" - } - }, - { - "package": "Validation", - "repositoryURL": "https://github.com/vapor/validation.git", - "state": { - "branch": null, - "revision": "ab6c5a352d97c8687b91ed4963aef8e7cfe0795b", - "version": "2.0.0" - } - }, - { - "package": "Vapor", - "repositoryURL": "https://github.com/vapor/vapor", - "state": { - "branch": null, - "revision": "54632a6c1e7ecd9923c0d00b612de936de1c4745", - "version": "3.0.8" - } - }, { "package": "WebSocket", - "repositoryURL": "https://github.com/vapor/websocket.git", + "repositoryURL": "https://github.com/vapor/websocket", "state": { "branch": null, "revision": "141cb4d3814dc8062cb0b2f43e72801b5dfcf272", diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index aaa74b48a..99554b322 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -133,6 +133,12 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu return doneFuture } + + future.catch({[weak self] error in + guard let this = self else { return } + + this.handleClose(reason: error) + }) } internal func closeWebSockets(fast: Bool = false) { From cc7c8e09b0421ccba933e1325cfad05b30311cc9 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sat, 22 Sep 2018 11:50:00 -0400 Subject: [PATCH 003/104] move c deps to package system targs instead of repos --- Package.resolved | 38 +++++-------------- Package.swift | 9 ++--- Sources/COPUS/module.modulemap | 5 +++ Sources/COPUS/shim.h | 22 +++++++++++ Sources/DiscordOpus/configure.c | 33 ---------------- Sources/DiscordOpus/include/configure.h | 26 ------------- Sources/Sodium/module.modulemap | 5 +++ Sources/Sodium/shim.h | 6 +++ .../Voice/DiscordOpusCoding.swift | 1 - .../Voice/DiscordVoiceDataSource.swift | 1 - .../Voice/DiscordVoiceDecoder.swift | 1 - 11 files changed, 52 insertions(+), 95 deletions(-) create mode 100644 Sources/COPUS/module.modulemap create mode 100644 Sources/COPUS/shim.h delete mode 100644 Sources/DiscordOpus/configure.c delete mode 100644 Sources/DiscordOpus/include/configure.h create mode 100644 Sources/Sodium/module.modulemap create mode 100644 Sources/Sodium/shim.h diff --git a/Package.resolved b/Package.resolved index b23cd59f3..dc2e62d58 100644 --- a/Package.resolved +++ b/Package.resolved @@ -10,22 +10,13 @@ "version": "1.1.0" } }, - { - "package": "COPUS", - "repositoryURL": "https://github.com/nuclearace/copus", - "state": { - "branch": null, - "revision": "365743902efc1c93730757cea288bef4b90637a0", - "version": "2.0.0" - } - }, { "package": "Core", "repositoryURL": "https://github.com/vapor/core.git", "state": { "branch": null, - "revision": "7f56a09995bf3c8df562be456bdcda405d9d0678", - "version": "3.4.1" + "revision": "eb876a758733166a4fb20f3f0a17b480c5ea563e", + "version": "3.4.3" } }, { @@ -42,17 +33,8 @@ "repositoryURL": "https://github.com/vapor/http.git", "state": { "branch": null, - "revision": "8123ea00e9858b369cd168d0303d33e7d3804d19", - "version": "3.0.7" - } - }, - { - "package": "Sodium", - "repositoryURL": "https://github.com/nuclearace/Sodium", - "state": { - "branch": null, - "revision": "5812a3d879b77aae0fdfbd62d0e8354e914d15ae", - "version": "2.0.0" + "revision": "9e3eff9dfa4df7fc282bf27f801c72b3ffbfd984", + "version": "3.1.4" } }, { @@ -69,8 +51,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "c6acf77fc9a310c0ebf8d5f73f285d1639801d2f", - "version": "1.9.0" + "revision": "5d8148c8b45dfb449276557f22120694567dd1d2", + "version": "1.9.5" } }, { @@ -78,8 +60,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "6617eb0d3afcb12170594968df01ca63afb58ac5", - "version": "1.2.0" + "revision": "8380fa29a2af784b067d8ee01c956626ca29f172", + "version": "1.3.1" } }, { @@ -105,8 +87,8 @@ "repositoryURL": "https://github.com/vapor/websocket", "state": { "branch": null, - "revision": "141cb4d3814dc8062cb0b2f43e72801b5dfcf272", - "version": "1.0.1" + "revision": "149af03348f60ac8b84defdf277112d62fd8c704", + "version": "1.0.2" } }, { diff --git a/Package.swift b/Package.swift index a86503b15..1b3ae33cf 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.0 +// swift-tools-version:4.2 // The MIT License (MIT) // Copyright (c) 2016 Erik Little @@ -20,12 +20,10 @@ import PackageDescription var deps: [Package.Dependency] = [ - .package(url: "https://github.com/nuclearace/copus", .upToNextMinor(from: "2.0.0")), - .package(url: "https://github.com/nuclearace/Sodium", .upToNextMinor(from: "2.0.0")), .package(url: "https://github.com/vapor/websocket", .upToNextMinor(from: "1.0.0")), ] -var targetDeps: [Target.Dependency] = ["DiscordOpus", "WebSocket"] +var targetDeps: [Target.Dependency] = ["WebSocket", "COPUS", "Sodium"] #if !os(Linux) deps += [.package(url: "https://github.com/daltoniam/Starscream", .upToNextMinor(from: "3.0.0")),] @@ -41,7 +39,8 @@ let package = Package( dependencies: deps, targets: [ .target(name: "SwiftDiscord", dependencies: targetDeps), - .target(name: "DiscordOpus"), + .systemLibrary(name: "COPUS", pkgConfig: "opus"), + .systemLibrary(name: "Sodium"), .testTarget(name: "SwiftDiscordTests", dependencies: ["SwiftDiscord"]), ] ) diff --git a/Sources/COPUS/module.modulemap b/Sources/COPUS/module.modulemap new file mode 100644 index 000000000..c6c5ed1c9 --- /dev/null +++ b/Sources/COPUS/module.modulemap @@ -0,0 +1,5 @@ +module COPUS [system] { + header "shim.h" + link "opus" + export * +} diff --git a/Sources/COPUS/shim.h b/Sources/COPUS/shim.h new file mode 100644 index 000000000..f2a19dca3 --- /dev/null +++ b/Sources/COPUS/shim.h @@ -0,0 +1,22 @@ +#ifndef __COPUS_SHIM_H__ +#define __COPUS_SHIM_H__ + +#include + +int configure_encoder(OpusEncoder *enc, int bitrate, int vbr) +{ + int err; + + err = opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); + err = opus_encoder_ctl(enc, OPUS_SET_VBR(vbr)); + + return err; +} + +int configure_decoder(OpusDecoder *dec, int gain) +{ + return opus_decoder_ctl(dec, OPUS_SET_GAIN(gain)); +} + + +#endif diff --git a/Sources/DiscordOpus/configure.c b/Sources/DiscordOpus/configure.c deleted file mode 100644 index 678a49c86..000000000 --- a/Sources/DiscordOpus/configure.c +++ /dev/null @@ -1,33 +0,0 @@ -// The MIT License (MIT) -// Copyright (c) 2017 Erik Little - -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without -// limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the -// Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -// Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO -// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -#include "configure.h" - -int configure_encoder(OpusEncoder *enc, int bitrate, int vbr) -{ - int err; - - err = opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); - err = opus_encoder_ctl(enc, OPUS_SET_VBR(vbr)); - - return err; -} - -int configure_decoder(OpusDecoder *dec, int gain) -{ - return opus_decoder_ctl(dec, OPUS_SET_GAIN(gain)); -} diff --git a/Sources/DiscordOpus/include/configure.h b/Sources/DiscordOpus/include/configure.h deleted file mode 100644 index 67c7b1577..000000000 --- a/Sources/DiscordOpus/include/configure.h +++ /dev/null @@ -1,26 +0,0 @@ -// The MIT License (MIT) -// Copyright (c) 2017 Erik Little - -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without -// limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the -// Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -// Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO -// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -#ifndef configure_h -#define configure_h - -#include - -int configure_encoder(OpusEncoder *enc, int bitrate, int vbr); -int configure_decoder(OpusDecoder *dec, int gain); - -#endif /* configure_h */ diff --git a/Sources/Sodium/module.modulemap b/Sources/Sodium/module.modulemap new file mode 100644 index 000000000..3bfeacd38 --- /dev/null +++ b/Sources/Sodium/module.modulemap @@ -0,0 +1,5 @@ +module Sodium [system] { + header "shim.h" + link "sodium" + export * +} diff --git a/Sources/Sodium/shim.h b/Sources/Sodium/shim.h new file mode 100644 index 000000000..29bd32ee4 --- /dev/null +++ b/Sources/Sodium/shim.h @@ -0,0 +1,6 @@ +#ifndef __SODIUM_SHIM_H__ +#define __SODIUM_SHIM_H__ + +#include + +#endif diff --git a/Sources/SwiftDiscord/Voice/DiscordOpusCoding.swift b/Sources/SwiftDiscord/Voice/DiscordOpusCoding.swift index 2f55f23c7..0bd19aad0 100644 --- a/Sources/SwiftDiscord/Voice/DiscordOpusCoding.swift +++ b/Sources/SwiftDiscord/Voice/DiscordOpusCoding.swift @@ -16,7 +16,6 @@ // DEALINGS IN THE SOFTWARE. import COPUS -import DiscordOpus import Foundation /// Declares that a type has enough information to encode/decode Opus data. diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift index b8541a33e..5b617be8e 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift @@ -16,7 +16,6 @@ // DEALINGS IN THE SOFTWARE. import COPUS -import DiscordOpus import Dispatch import Foundation diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift index b1aeb0aa1..fbaee8e82 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift @@ -16,7 +16,6 @@ // DEALINGS IN THE SOFTWARE. import COPUS -import DiscordOpus import Foundation /// Class that decodes Opus voice data into raw PCM data for a VoiceEngine. It can decode multiple streams. Decoding is From 6833ee9b24f270ee55882271f1f2a0b592942f39 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sat, 6 Oct 2018 09:04:52 -0400 Subject: [PATCH 004/104] update deps --- Package.resolved | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Package.resolved b/Package.resolved index dc2e62d58..4b8297c7a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/vapor/core.git", "state": { "branch": null, - "revision": "eb876a758733166a4fb20f3f0a17b480c5ea563e", - "version": "3.4.3" + "revision": "96ce86ebf9198328795c4b9cb711489460be083c", + "version": "3.4.4" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/vapor/crypto.git", "state": { "branch": null, - "revision": "4b85405430df1892ee3aa1554bdb477e96cf46ad", - "version": "3.2.0" + "revision": "5605334590affd4785a5839806b4504407e054ac", + "version": "3.3.0" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/vapor/http.git", "state": { "branch": null, - "revision": "9e3eff9dfa4df7fc282bf27f801c72b3ffbfd984", - "version": "3.1.4" + "revision": "272b22be8cb3364e42a4701c2e0676e37480ec5a", + "version": "3.1.5" } }, { From d52672e5576bcab5bbd8631e251c089a8ca844ca Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sat, 6 Oct 2018 11:13:35 -0400 Subject: [PATCH 005/104] fiddle around with connecting --- .../SwiftDiscord/Gateway/DiscordEngine.swift | 8 +++---- .../Gateway/DiscordEngineSpec.swift | 22 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift index 246584d0a..dfc8833bd 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift @@ -16,12 +16,8 @@ // DEALINGS IN THE SOFTWARE. import Foundation -//#if !os(Linux) -//import Starscream -//#else import NIO import WebSocket -//#endif import Dispatch #if os(macOS) @@ -40,7 +36,7 @@ open class DiscordEngine : DiscordEngineSpec { /// The url for the gateway. open var connectURL: String { - return DiscordEndpointGateway.gatewayURL + "/?v=6" + return DiscordEndpointGateway.gatewayURL + "/?v=6&encoding=json" } /// The type of DiscordEngineSpec. Used to correctly fire events. @@ -401,6 +397,8 @@ open class DiscordEngine : DiscordEngineSpec { /// - parameter milliseconds: The heartbeat interval /// public func startHeartbeat(milliseconds: Int) { + DefaultDiscordLogger.Logger.debug("Starting heartbeat, shard: \(shardNum), \(milliseconds)ms", type: logType) + heartbeatInterval = milliseconds sendHeartbeat() diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index 99554b322..2817db450 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -105,22 +105,24 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu /// Starts the connection to the Discord gateway. /// public func connect() { + runloop.execute(self._connect) + } + + private func _connect() { DefaultDiscordLogger.Logger.log("Connecting to \(connectURL), \(description)", type: "DiscordWebSocketable") DefaultDiscordLogger.Logger.log("Attaching WebSocket, shard: \(description)", type: "DiscordWebSocketable") let url = URL(string: connectURL)! let path = url.path.isEmpty ? "/" : url.path - + let doneFuture = runloop.newSucceededFuture(result: ()) let future = HTTPClient.webSocket(scheme: .wss, - hostname: url.host!, - port: url.port, - path: path, - on: runloop + hostname: url.host!, + port: url.port, + path: path, + on: runloop ) - let doneFuture = runloop.newSucceededFuture(result: ()) - - _ = future.then {[weak self] ws -> EventLoopFuture<()> in + future.then {[weak self] ws -> EventLoopFuture<()> in guard let this = self else { return doneFuture } DefaultDiscordLogger.Logger.log("Websocket connected, shard: \(this.description)", type: "DiscordWebSocketable") @@ -132,9 +134,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu this.startHandshake() return doneFuture - } - - future.catch({[weak self] error in + }.catch({[weak self] error in guard let this = self else { return } this.handleClose(reason: error) From 63120e4ed201d2900f046018c9dc8bf31e645e55 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 7 Oct 2018 11:54:10 -0400 Subject: [PATCH 006/104] start adding back voice support --- Package.swift | 3 +- .../Voice/DiscordVoiceEngine.swift | 224 +++++++++--------- 2 files changed, 115 insertions(+), 112 deletions(-) diff --git a/Package.swift b/Package.swift index 1b3ae33cf..8988edc5c 100644 --- a/Package.swift +++ b/Package.swift @@ -21,9 +21,10 @@ import PackageDescription var deps: [Package.Dependency] = [ .package(url: "https://github.com/vapor/websocket", .upToNextMinor(from: "1.0.0")), + .package(url: "https://github.com/IBM-Swift/BlueSocket", .upToNextMinor(from: "1.0.0")) ] -var targetDeps: [Target.Dependency] = ["WebSocket", "COPUS", "Sodium"] +var targetDeps: [Target.Dependency] = ["WebSocket", "COPUS", "Sodium", "Socket"] #if !os(Linux) deps += [.package(url: "https://github.com/daltoniam/Starscream", .upToNextMinor(from: "3.0.0")),] diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift index 2f5817510..dda6f808d 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift @@ -17,12 +17,8 @@ import Foundation import Dispatch -//#if !os(Linux) -//import Starscream -//#else import WebSocket -//#endif -//import Sockets +import Socket import Sodium /// @@ -36,6 +32,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { case decryptionError case encryptionError case ipExtraction + case unknown } // MARK: Properties @@ -109,8 +106,8 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// Our SSRC public private(set) var ssrc: UInt32 = 0 -// /// The UDP socket that is used to send/receive voice data -// public private(set) var udpSocket: UDPInternetSocket? + /// The UDP socket that is used to send/receive voice data + public private(set) var udpSocket: Socket? /// Our UDP port public private(set) var udpPort = -1 @@ -187,17 +184,12 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { // MARK: Methods private func closeOutEngine() { -// guard !closed else { return } -// -// do { -// try udpSocket?.close() -// } catch { -// self.error(message: "Error trying to close voice engine udp socket") -// } -// -// closed = true -// connected = false -// sendTimer.cancel() + guard !closed else { return } + + udpSocket?.close() + closed = true + connected = false + sendTimer.cancel() } private func configureTimer() { @@ -244,7 +236,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { return rtpHeader + Array(UnsafeBufferPointer(start: encrypted, count: packetSize)) } - private func decryptVoiceData(_ data: [UInt8]) throws -> [UInt8] { + private func decryptVoiceData(_ data: Data) throws -> [UInt8] { // TODO this isn't totally correct, there might be an extension after the rtp header let rtpHeader = Array(data.prefix(12)) let voiceData = Array(data.dropFirst(12)) @@ -276,10 +268,10 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { voiceDelegate?.voiceEngineDidDisconnect(self) } - private func extractIPAndPort(from bytes: [UInt8]) throws -> (String, Int) { + private func extractIPAndPort(from bytes: Data) throws -> (String, Int) { DefaultDiscordLogger.Logger.debug("Extracting ip and port from \(bytes)", type: DiscordVoiceEngine.logType) - let ipData = Data(bytes: bytes.dropLast(2)) + let ipData = bytes.dropLast(2) let portBytes = Array(bytes.suffix(from: bytes.endIndex.advanced(by: -2))) let port = (Int(portBytes[0]) | Int(portBytes[1])) << 8 @@ -292,24 +284,25 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { // https://discordapp.com/developers/docs/topics/voice-connections#ip-discovery private func findIP() { -// udpQueueWrite.async { -// guard let udpSocket = self.udpSocket else { return } -// -// // print("Finding IP") -// let discoveryData = [UInt8](repeating: 0x00, count: 70) -// -// do { -// try udpSocket.sendto(data: discoveryData) -// -// let (data, _) = try udpSocket.recvfrom(maxBytes: 70) -// let (ip, port) = try self.extractIPAndPort(from: data) -// -// self.selectProtocol(with: ip, on: port) -// } catch { -// self.error(message: "Something went wrong extracting the ip and port \(error)") -// self.disconnect() -// } -// } + udpQueueWrite.async { + guard let udpSocket = self.udpSocket else { return } + + // print("Finding IP") + let discoveryData = [UInt8](repeating: 0x00, count: 70) + + do { + var data = Data() + try udpSocket.write(from: Data(bytes: discoveryData)) + + _ = try udpSocket.readDatagram(into: &data) + let (ip, port) = try self.extractIPAndPort(from: data) + + self.selectProtocol(with: ip, on: port) + } catch { + self.error(message: "Something went wrong extracting the ip and port \(error)") + self.disconnect() + } + } } private func getNewDataSource() { @@ -490,39 +483,41 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { private func readSocket() { // TODO refactor to be non-blocking -// udpQueueRead.async {[weak self] in -// guard let socket = self?.udpSocket, self?.connected ?? false else { return } -// -// do { -// let (data, _) = try socket.recvfrom(maxBytes: 4096) -// -// DefaultDiscordLogger.Logger.debug("Received data \(data)", type: "DiscordVoiceEngine") -// -// guard let this = self else { return } -// -// let packet = DiscordOpusVoiceData(voicePacket: try this.decryptVoiceData(data)) -// -// if this.config.decodeVoice { -// this.voiceDelegate?.voiceEngine(this, -// didReceiveRawVoiceData: try this.decoderSession.decode(packet)) -// } else { -// this.voiceDelegate?.voiceEngine(this, didReceiveOpusVoiceData: packet) -// } -// } catch DiscordVoiceError.initialPacket { -// DefaultDiscordLogger.Logger.debug("Got initial packet", type: DiscordVoiceEngine.logType) -// } catch DiscordVoiceError.decodeFail { -// DefaultDiscordLogger.Logger.debug("Failed to decode a packet", type: DiscordVoiceEngine.logType) -// } catch EngineError.decryptionError { -// self?.error(message: "Error decrypting voice packet") -// } catch let err { -// self?.error(message: "Error reading voice data from udp socket \(err)") -// self?.disconnect() -// -// return -// } -// -// self?.readSocket() -// } + udpQueueRead.async {[weak self] in + guard let socket = self?.udpSocket, self?.connected ?? false else { return } + + do { + var data = Data() + + _ = try socket.readDatagram(into: &data) + + DefaultDiscordLogger.Logger.debug("Received data \(data)", type: "DiscordVoiceEngine") + + guard let this = self else { return } + + let packet = DiscordOpusVoiceData(voicePacket: try this.decryptVoiceData(data)) + + if this.config.decodeVoice { + this.voiceDelegate?.voiceEngine(this, + didReceiveRawVoiceData: try this.decoderSession.decode(packet)) + } else { + this.voiceDelegate?.voiceEngine(this, didReceiveOpusVoiceData: packet) + } + } catch DiscordVoiceError.initialPacket { + DefaultDiscordLogger.Logger.debug("Got initial packet", type: DiscordVoiceEngine.logType) + } catch DiscordVoiceError.decodeFail { + DefaultDiscordLogger.Logger.debug("Failed to decode a packet", type: DiscordVoiceEngine.logType) + } catch EngineError.decryptionError { + self?.error(message: "Error decrypting voice packet") + } catch let err { + self?.error(message: "Error reading voice data from udp socket \(err)") + self?.disconnect() + + return + } + + self?.readSocket() + } } /// @@ -605,28 +600,28 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// - parameter data: An Opus encoded packet. /// private func sendVoiceData(_ data: [UInt8]) { -// guard let udpSocket = self.udpSocket, let frameSize = source?.frameSize, secret != nil else { return } -// -// if !speaking { -// sendSpeaking(true) -// } -// -// DefaultDiscordLogger.Logger.debug("Should send voice data: \(data.count) bytes", -// type: DiscordVoiceEngine.logType) -// -// do { -// try udpSocket.sendto(data: createVoicePacket(data)) -// } catch EngineError.encryptionError { -// error(message: "Error encrypting packet") -// } catch let err { -// error(message: "Failed sending voice packet \(err)") -// disconnect() -// -// return -// } -// -// sequenceNum = sequenceNum &+ 1 -// timestamp = timestamp &+ UInt32(frameSize) + guard let udpSocket = self.udpSocket, let frameSize = source?.frameSize, secret != nil else { return } + + if !speaking { + sendSpeaking(true) + } + + DefaultDiscordLogger.Logger.debug("Should send voice data: \(data.count) bytes", + type: DiscordVoiceEngine.logType) + + do { + try udpSocket.write(from: Data(bytes: createVoicePacket(data))) + } catch EngineError.encryptionError { + error(message: "Error encrypting packet") + } catch let err { + error(message: "Failed sending voice packet \(err)") + disconnect() + + return + } + + sequenceNum = sequenceNum &+ 1 + timestamp = timestamp &+ UInt32(frameSize) } #if !os(iOS) @@ -687,22 +682,29 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { } private func startUDP() { -// guard udpPort != -1 else { return } -// -// let base = voiceServerInformation.endpoint.components(separatedBy: ":")[0] -// let udpEndpoint = InternetAddress(hostname: base, port: UInt16(udpPort)) -// -// DefaultDiscordLogger.Logger.debug("Starting voice UDP connection", type: DiscordVoiceEngine.logType) -// -// guard let client = try? UDPInternetSocket(address: udpEndpoint) else { -// disconnect() -// -// return -// } -// -// udpSocket = client -// -// // Begin async UDP setup -// findIP() + guard udpPort != -1 else { return } + + let base = voiceServerInformation.endpoint.components(separatedBy: ":")[0] + + DefaultDiscordLogger.Logger.debug("Starting voice UDP connection", type: DiscordVoiceEngine.logType) + + do { + guard let sig = try Socket.Signature( + protocolFamily: .inet, + socketType: .datagram, + proto: .udp, + hostname: base, + port: Int32(udpPort) + ) else { + throw EngineError.unknown + } + + udpSocket = try Socket.create(connectedUsing: sig) + + // Begin async UDP setup + findIP() + } catch { + // TODO Handle voice error disconnect from voice + } } } From aaf0374b26cf65f743e00cc0b829fd1dffa3d0c4 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 27 Aug 2019 19:31:41 +0200 Subject: [PATCH 007/104] Remove .swift-version Allow the project to build under Swift 5.1 by removing the version constraint. --- .swift-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .swift-version diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 4d54daddb..000000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -4.0.2 From f58fafdce1abd61271f80e509b4f72a1110bdb58 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 27 Aug 2019 18:46:13 +0200 Subject: [PATCH 008/104] Add FoundationNetworking imports In Swift 5.1 networking-related types (such as HTTPURLResponse) have been moved into a separate FoundationNetworking module. To maintain compatibility with older versions, a preprocessor condition is used to test whether this module is available. --- Sources/SwiftDiscord/Channel/DiscordChannel.swift | 4 ++++ Sources/SwiftDiscord/DiscordJSON.swift | 3 +++ Sources/SwiftDiscord/Guild/DiscordGuild.swift | 3 +++ Sources/SwiftDiscord/Rest/DiscordEndpoint.swift | 3 +++ .../SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift | 3 +++ .../SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift | 3 +++ .../SwiftDiscord/Rest/DiscordEndpointConsumer+Invites.swift | 3 +++ Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift | 3 +++ .../SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift | 3 +++ Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift | 3 +++ Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift | 4 ++++ Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift | 4 ++++ 12 files changed, 39 insertions(+) diff --git a/Sources/SwiftDiscord/Channel/DiscordChannel.swift b/Sources/SwiftDiscord/Channel/DiscordChannel.swift index d9b34a5f1..ef4941916 100644 --- a/Sources/SwiftDiscord/Channel/DiscordChannel.swift +++ b/Sources/SwiftDiscord/Channel/DiscordChannel.swift @@ -16,6 +16,10 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + import class Dispatch.DispatchSemaphore /// Protocol that declares a type will be a Discord channel. diff --git a/Sources/SwiftDiscord/DiscordJSON.swift b/Sources/SwiftDiscord/DiscordJSON.swift index 4f6a7d63f..43b6d14a2 100644 --- a/Sources/SwiftDiscord/DiscordJSON.swift +++ b/Sources/SwiftDiscord/DiscordJSON.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif enum JSON { case array([Any]) diff --git a/Sources/SwiftDiscord/Guild/DiscordGuild.swift b/Sources/SwiftDiscord/Guild/DiscordGuild.swift index 27425da67..9f64dc27f 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuild.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuild.swift @@ -17,6 +17,9 @@ import class Dispatch.DispatchSemaphore import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif /// Represents a Guild. public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index ee5040fdb..87f52478f 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif // TODO Group DM // TODO Add guild member diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift index 5abbfe5bd..f885419fe 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift index afb2439aa..fbdac5bfa 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Invites.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Invites.swift index 6be623f4a..8db27fe7b 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Invites.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Invites.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift index 98ca2dd64..2f5c06679 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift index 6ee58b3ab..04967e0ab 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift index 0ce041d54..80a8f502b 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif /// /// Protocol that declares a type will be a consumer of the Discord REST API. diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift index d55a04aa1..546f4f14a 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift @@ -16,6 +16,10 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + import Dispatch struct DiscordEndpointGateway { diff --git a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift index 4398b88c3..cc2134b19 100644 --- a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift +++ b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift @@ -16,6 +16,10 @@ // DEALINGS IN THE SOFTWARE. import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + import Dispatch internal typealias DiscordRequestCallback = (Data?, HTTPURLResponse?, Error?) -> () From 8045ab64642a5972d6819c1f42a26c02ad2f26d3 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 28 Aug 2019 00:22:44 +0200 Subject: [PATCH 009/104] Upgrade vapor/WebSocket to 1.1.x --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 8988edc5c..864143e3e 100644 --- a/Package.swift +++ b/Package.swift @@ -20,7 +20,7 @@ import PackageDescription var deps: [Package.Dependency] = [ - .package(url: "https://github.com/vapor/websocket", .upToNextMinor(from: "1.0.0")), + .package(url: "https://github.com/vapor/websocket", .upToNextMinor(from: "1.1.2")), .package(url: "https://github.com/IBM-Swift/BlueSocket", .upToNextMinor(from: "1.0.0")) ] From 0816a04c2b2fc0f3fd2e9d56844b3d83beef7846 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 28 Aug 2019 14:32:52 +0200 Subject: [PATCH 010/104] Implement WebSocket using nio-websocket-client Since the vapor/websocket has known issues with maintaining a persistent connection, use vapor/nio-websocket-client as a WebSocket backend. --- Package.resolved | 61 +++---------------- Package.swift | 10 +-- .../SwiftDiscord/Gateway/DiscordEngine.swift | 6 +- .../Gateway/DiscordEngineSpec.swift | 49 +++++++-------- .../SwiftDiscord/Gateway/DiscordGateway.swift | 2 +- .../Voice/DiscordVoiceEngine.swift | 4 +- 6 files changed, 36 insertions(+), 96 deletions(-) diff --git a/Package.resolved b/Package.resolved index 7aadfa796..c85060d24 100644 --- a/Package.resolved +++ b/Package.resolved @@ -11,30 +11,12 @@ } }, { - "package": "Core", - "repositoryURL": "https://github.com/vapor/core.git", + "package": "nio-websocket-client", + "repositoryURL": "https://github.com/vapor/nio-websocket-client", "state": { "branch": null, - "revision": "c64f63cb21631010952f7abfef719d376ab6a441", - "version": "3.9.1" - } - }, - { - "package": "Crypto", - "repositoryURL": "https://github.com/vapor/crypto.git", - "state": { - "branch": null, - "revision": "df8eb7d8ae51787b3a0628aa3975e67666da936c", - "version": "3.3.3" - } - }, - { - "package": "HTTP", - "repositoryURL": "https://github.com/vapor/http.git", - "state": { - "branch": null, - "revision": "3808ed0401379b6e9f4a053f03090ea9d658caa9", - "version": "3.2.1" + "revision": "e24870ba2350b613e71322caf79d138e139819df", + "version": null } }, { @@ -42,8 +24,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "ba7970fe396e8198b84c6c1b44b38a1d4e2eb6bd", - "version": "1.14.1" + "revision": "32760eae40e6b7cb81d4d543bb0a9f548356d9a2", + "version": "2.7.1" } }, { @@ -51,35 +33,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "0f3999f3e3c359cc74480c292644c3419e44a12f", - "version": "1.4.0" - } - }, - { - "package": "swift-nio-ssl-support", - "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", - "state": { - "branch": null, - "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", - "version": "1.0.0" - } - }, - { - "package": "swift-nio-zlib-support", - "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", - "state": { - "branch": null, - "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version": "1.0.0" - } - }, - { - "package": "WebSocket", - "repositoryURL": "https://github.com/vapor/websocket", - "state": { - "branch": null, - "revision": "149af03348f60ac8b84defdf277112d62fd8c704", - "version": "1.0.2" + "revision": "f5dd7a60ff56f501ff7bf9be753e4b1875bfaf20", + "version": "2.4.0" } } ] diff --git a/Package.swift b/Package.swift index 864143e3e..6eb2b813d 100644 --- a/Package.swift +++ b/Package.swift @@ -20,17 +20,11 @@ import PackageDescription var deps: [Package.Dependency] = [ - .package(url: "https://github.com/vapor/websocket", .upToNextMinor(from: "1.1.2")), + .package(url: "https://github.com/vapor/nio-websocket-client", .revision("e24870ba2350b613e71322caf79d138e139819df")), .package(url: "https://github.com/IBM-Swift/BlueSocket", .upToNextMinor(from: "1.0.0")) ] -var targetDeps: [Target.Dependency] = ["WebSocket", "COPUS", "Sodium", "Socket"] - -#if !os(Linux) -deps += [.package(url: "https://github.com/daltoniam/Starscream", .upToNextMinor(from: "3.0.0")),] -targetDeps += ["Starscream"] -#endif - +var targetDeps: [Target.Dependency] = ["AsyncWebSocketClient", "COPUS", "Sodium", "Socket"] let package = Package( name: "SwiftDiscord", diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift index dfc8833bd..d24e1f4c8 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift @@ -17,7 +17,7 @@ import Foundation import NIO -import WebSocket +import AsyncWebSocketClient import Dispatch #if os(macOS) @@ -99,9 +99,7 @@ open class DiscordEngine : DiscordEngineSpec { public var sessionId: String? /// The underlying WebSocket. - /// - /// On Linux this is a WebSockets.WebSocket. While on macOS/iOS this is a Starscream.WebSocket - public var websocket: WebSocket? + public var websocket: WebSocketClient.Socket? /// Whether this engine is connected to the gateway. public internal(set) var connected = false diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index 2817db450..c7f3ccd50 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -18,8 +18,7 @@ import Dispatch import Foundation import NIO -import HTTP -import WebSocket +import AsyncWebSocketClient /// Declares that a type will be an Engine for the Discord Gateway. public protocol DiscordEngineSpec : DiscordShard { @@ -48,9 +47,8 @@ public protocol DiscordWebSocketable : AnyObject { /// The queue WebSockets do their parsing on. var parseQueue: DispatchQueue { get } - /// A reference to the underlying WebSocket. This is a WebSockets.Websocket on Linux and Starscream.WebSocket on - /// macOS/iOS. - var websocket: WebSocket? { get set } + /// A reference to the underlying WebSocket. + var websocket: WebSocketClient.Socket? { get set } // MARK: Methods @@ -59,8 +57,6 @@ public protocol DiscordWebSocketable : AnyObject { /// /// Override if you need to provide custom handlers. /// - /// Note: You should handle both WebSockets.WebSocket and Starscream.WebSocket handlers. - /// func attachWebSocketHandlers() /// @@ -84,7 +80,7 @@ public protocol DiscordWebSocketable : AnyObject { public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRunLoopable { /// Default implementation. public func attachWebSocketHandlers() { - websocket?.onText {[weak self] ws, text in + websocket?.onText { [weak self] ws, text in guard let this = self else { return } DefaultDiscordLogger.Logger.debug("\(this.description), Got text: \(text)", type: "DiscordWebSocketable") @@ -92,13 +88,19 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu this.parseGatewayMessage(text) } - websocket?.onClose.do {[weak self] _ in + websocket?.onCloseCode { [weak self] code in guard let this = self else { return } - DefaultDiscordLogger.Logger.log("WebSocket closed, \(this.description);", type: "DiscordWebSocketable") + DefaultDiscordLogger.Logger.log("WebSocket closed, code: \(code), \(this.description);", type: "DiscordWebSocketable") this.handleClose(reason: nil) - }.catch {_ in } + } + + websocket?.onError { [weak self] ws, err in + guard let this = self else { return } + + DefaultDiscordLogger.Logger.log("WebSocket errored, \(err), \(this.description)", type: "DiscordWebSocketable") + } } /// @@ -114,16 +116,13 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu let url = URL(string: connectURL)! let path = url.path.isEmpty ? "/" : url.path - let doneFuture = runloop.newSucceededFuture(result: ()) - let future = HTTPClient.webSocket(scheme: .wss, - hostname: url.host!, - port: url.port, - path: path, - on: runloop - ) - - future.then {[weak self] ws -> EventLoopFuture<()> in - guard let this = self else { return doneFuture } + let wsClient = WebSocketClient(eventLoopGroupProvider: .shared(runloop)) + wsClient.connect( + host: url.host!, + port: url.port ?? 443, + uri: path + ) { [weak self] ws in + guard let this = self else { return } DefaultDiscordLogger.Logger.log("Websocket connected, shard: \(this.description)", type: "DiscordWebSocketable") @@ -132,13 +131,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu this.attachWebSocketHandlers() this.startHandshake() - - return doneFuture - }.catch({[weak self] error in - guard let this = self else { return } - - this.handleClose(reason: error) - }) + } } internal func closeWebSockets(fast: Bool = false) { diff --git a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift index 611894063..c66eda297 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift @@ -100,7 +100,7 @@ public extension DiscordGatewayable where Self: DiscordWebSocketable & DiscordRu DefaultDiscordLogger.Logger.debug("Sending ws: \(payloadString)", type: description) runloop.execute { - self.websocket?.send(payloadString) + self.websocket?.send(text: payloadString) } } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift index b89667802..42dda11c9 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift @@ -17,7 +17,7 @@ import Foundation import Dispatch -import WebSocket +import AsyncWebSocketClient import Socket import Sodium @@ -86,7 +86,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { } /// The underlying websocket. - public var websocket: WebSocket? + public var websocket: WebSocketClient.Socket? /// The voice engine's delegate. public private(set) weak var voiceDelegate: DiscordVoiceEngineDelegate? From 49eda4cf43f5ad57b04b51894748b53fd48938dc Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 28 Aug 2019 19:05:12 +0200 Subject: [PATCH 011/104] Setup TLS in WebSocket configuration --- .../SwiftDiscord/Gateway/DiscordEngineSpec.swift | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index c7f3ccd50..a68284ed4 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -116,8 +116,11 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu let url = URL(string: connectURL)! let path = url.path.isEmpty ? "/" : url.path - let wsClient = WebSocketClient(eventLoopGroupProvider: .shared(runloop)) - wsClient.connect( + let wsClient = WebSocketClient(eventLoopGroupProvider: .shared(runloop), configuration: .init( + tlsConfiguration: .clientDefault, + maxFrameSize: 1 << 31 + )) + let future = wsClient.connect( host: url.host!, port: url.port ?? 443, uri: path @@ -132,6 +135,14 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu this.attachWebSocketHandlers() this.startHandshake() } + + future.whenFailure { [weak self] err in + guard let this = self else { return } + + DefaultDiscordLogger.Logger.log("Websocket errored, closing: \(err), \(this.description)", type: "DiscordWebSocketable") + + this.handleClose(reason: err) + } } internal func closeWebSockets(fast: Bool = false) { From 62df10f840599a97954e524cd18b85c1466f3a4d Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 28 Aug 2019 20:10:17 +0200 Subject: [PATCH 012/104] Update nio-websocket-client --- Package.resolved | 2 +- Package.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.resolved b/Package.resolved index c85060d24..b889d850d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,7 +15,7 @@ "repositoryURL": "https://github.com/vapor/nio-websocket-client", "state": { "branch": null, - "revision": "e24870ba2350b613e71322caf79d138e139819df", + "revision": "f9a0955dff2b6a7a466cc26db2d061b682023197", "version": null } }, diff --git a/Package.swift b/Package.swift index 6eb2b813d..b843e6f3c 100644 --- a/Package.swift +++ b/Package.swift @@ -20,7 +20,7 @@ import PackageDescription var deps: [Package.Dependency] = [ - .package(url: "https://github.com/vapor/nio-websocket-client", .revision("e24870ba2350b613e71322caf79d138e139819df")), + .package(url: "https://github.com/vapor/nio-websocket-client", .revision("f9a0955dff2b6a7a466cc26db2d061b682023197")), .package(url: "https://github.com/IBM-Swift/BlueSocket", .upToNextMinor(from: "1.0.0")) ] From f8e6cd49fc1a63bfb54a4ca4eecc7ca4ac00dd74 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 28 Aug 2019 20:43:33 +0200 Subject: [PATCH 013/104] Fix compilation warnings Remove redundant 'public' modifiers for public extension functions and replace deprecated APIs. --- .../SwiftDiscord/Channel/DiscordChannel.swift | 18 ++++----- .../SwiftDiscord/Channel/DiscordMessage.swift | 6 +-- .../Gateway/DiscordEngineSpec.swift | 8 ++-- .../SwiftDiscord/Rest/DiscordEndpoint.swift | 6 +-- .../DiscordEndpointConsumer+Channels.swift | 37 +++++++++---------- .../Rest/DiscordEndpointConsumer+Guilds.swift | 32 ++++++++-------- .../DiscordEndpointConsumer+Invites.swift | 6 +-- .../Rest/DiscordEndpointConsumer+User.swift | 6 +-- .../DiscordEndpointConsumer+Webhooks.swift | 12 +++--- .../Rest/DiscordEndpointConsumer.swift | 2 +- .../Rest/DiscordRateLimiter.swift | 11 +++--- .../Voice/DiscordOpusCoding.swift | 2 +- .../Voice/DiscordVoiceDataSource.swift | 8 ++-- .../Voice/DiscordVoiceEngine.swift | 12 ++++-- 14 files changed, 85 insertions(+), 81 deletions(-) diff --git a/Sources/SwiftDiscord/Channel/DiscordChannel.swift b/Sources/SwiftDiscord/Channel/DiscordChannel.swift index ef4941916..ce91c6b6d 100644 --- a/Sources/SwiftDiscord/Channel/DiscordChannel.swift +++ b/Sources/SwiftDiscord/Channel/DiscordChannel.swift @@ -60,7 +60,7 @@ public extension DiscordChannel { // MARK: Properties /// - returns: The guild that this channel is associated with. Or nil if this channel has no guild. - public var guild: DiscordGuild? { + var guild: DiscordGuild? { return client?.guildForChannel(id) } @@ -69,7 +69,7 @@ public extension DiscordChannel { /// /// Deletes this channel. /// - public func delete(reason: String? = nil) { + func delete(reason: String? = nil) { guard let client = self.client else { return } DefaultDiscordLogger.Logger.log("Deleting channel: \(id)", type: "DiscordChannel") @@ -82,7 +82,7 @@ public extension DiscordChannel { /// /// - parameter options: An array of `DiscordEndpointOptions.ModifyChannel` /// - public func modifyChannel(options: [DiscordEndpoint.Options.ModifyChannel], reason: String? = nil) { + func modifyChannel(options: [DiscordEndpoint.Options.ModifyChannel], reason: String? = nil) { guard let client = self.client else { return } client.modifyChannel(id, options: options, reason: reason) @@ -97,7 +97,7 @@ public extension DiscordTextChannel { /// /// - parameter message: The message to pin /// - public func pinMessage(_ message: DiscordMessage) { + func pinMessage(_ message: DiscordMessage) { guard let client = self.client else { return } client.addPinnedMessage(message.id, on: id) @@ -108,7 +108,7 @@ public extension DiscordTextChannel { /// /// - parameter message: The message to delete /// - public func deleteMessage(_ message: DiscordMessage) { + func deleteMessage(_ message: DiscordMessage) { guard let client = self.client else { return } client.deleteMessage(message.id, on: id) @@ -119,7 +119,7 @@ public extension DiscordTextChannel { /// /// - parameter callback: The callback. /// - public func getPinnedMessages(callback: @escaping ([DiscordMessage], HTTPURLResponse?) -> ()) { + func getPinnedMessages(callback: @escaping ([DiscordMessage], HTTPURLResponse?) -> ()) { guard let client = self.client else { return callback([], nil) } client.getPinnedMessages(for: id) {pins, response in @@ -148,7 +148,7 @@ public extension DiscordTextChannel { /// /// - parameter message: The message to send. /// - public func send(_ message: DiscordMessage) { + func send(_ message: DiscordMessage) { guard let client = self.client else { return } client.sendMessage(message, to: id) @@ -157,7 +157,7 @@ public extension DiscordTextChannel { /// /// Sends that this user is typing on this channel. /// - public func triggerTyping() { + func triggerTyping() { guard let client = self.client else { return } client.triggerTyping(on: id) @@ -168,7 +168,7 @@ public extension DiscordTextChannel { /// /// - parameter message: The message to unpin. /// - public func unpinMessage(_ message: DiscordMessage) { + func unpinMessage(_ message: DiscordMessage) { guard let client = self.client else { return } client.deletePinnedMessage(message.id, on: id) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index b63f88aad..10d556638 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -223,7 +223,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { public extension DiscordMessage { /// Type of message - public enum MessageType : Int { + enum MessageType : Int { /// Default. case `default` @@ -250,7 +250,7 @@ public extension DiscordMessage { } /// Represents an action that be taken on a message. - public struct MessageActivity { + struct MessageActivity { /// Represents the type of activity. public enum ActivityType : Int { /// Join. @@ -275,7 +275,7 @@ public extension DiscordMessage { } /// Represents an application in a `DiscordMessage` object. - public struct MessageApplication { + struct MessageApplication { /// The id of this application. public let id: Snowflake diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index a68284ed4..8115c3356 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -79,7 +79,7 @@ public protocol DiscordWebSocketable : AnyObject { public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRunLoopable { /// Default implementation. - public func attachWebSocketHandlers() { + func attachWebSocketHandlers() { websocket?.onText { [weak self] ws, text in guard let this = self else { return } @@ -106,7 +106,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu /// /// Starts the connection to the Discord gateway. /// - public func connect() { + func connect() { runloop.execute(self._connect) } @@ -154,7 +154,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu return } - websocket?.close() + let _ = websocket?.close() } /// @@ -162,7 +162,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu /// /// - parameter message: The error message /// - public func error(message: String) { + func error(message: String) { DefaultDiscordLogger.Logger.error(message, type: description) } } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 87f52478f..18adb73b3 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -160,7 +160,7 @@ public extension DiscordEndpoint { /// /// * An HTTP Request for an Endpoint. This includes any associated data. /// - public enum EndpointRequest { + enum EndpointRequest { /// A GET request. case get(params: [String: String]?, extraHeaders: [DiscordHeader: String]?) @@ -255,7 +255,7 @@ public extension DiscordEndpoint { // MARK: Endpoint string calculation - public var description: String { + var description: String { switch self { case .baseURL: return "https://discordapp.com/api/v6" @@ -506,7 +506,7 @@ public enum DiscordHeader : String { public extension DiscordEndpoint { /// A namespace struct for endpoint options. - public struct Options { + struct Options { private init() {} /// Options when getting an audit log. diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift index f885419fe..8eab7c332 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift @@ -22,7 +22,7 @@ import FoundationNetworking public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation - public func addPinnedMessage(_ messageId: MessageID, + func addPinnedMessage(_ messageId: MessageID, on channelId: ChannelID, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { rateLimiter.executeRequest(endpoint: .pinnedMessage(channel: channelId, message: messageId), @@ -32,7 +32,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func bulkDeleteMessages(_ messages: [MessageID], + func bulkDeleteMessages(_ messages: [MessageID], on channelId: ChannelID, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { guard let contentData = JSON.encodeJSONData(["messages": messages.map({ $0.description })]) else { return } @@ -44,7 +44,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func createInvite(for channelId: ChannelID, + func createInvite(for channelId: ChannelID, options: [DiscordEndpoint.Options.CreateInvite], reason: String? = nil, callback: @escaping (DiscordInvite?, HTTPURLResponse?) -> ()) { @@ -87,7 +87,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func createReaction(for messageId: MessageID, + func createReaction(for messageId: MessageID, on channelId: ChannelID, emoji: String, callback: ((DiscordMessage?, HTTPURLResponse?) -> ())? = nil) { @@ -107,7 +107,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func deleteChannel(_ channelId: ChannelID, + func deleteChannel(_ channelId: ChannelID, reason: String? = nil, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { var extraHeaders = [DiscordHeader: String]() @@ -123,7 +123,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func deleteChannelPermission(_ overwriteId: OverwriteID, + func deleteChannelPermission(_ overwriteId: OverwriteID, on channelId: ChannelID, reason: String? = nil, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { @@ -140,7 +140,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func deleteMessage(_ messageId: MessageID, + func deleteMessage(_ messageId: MessageID, on channelId: ChannelID, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { rateLimiter.executeRequest(endpoint: .channelMessageDelete(channel: channelId, message: messageId), @@ -150,7 +150,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func deletePinnedMessage(_ messageId: MessageID, + func deletePinnedMessage(_ messageId: MessageID, on channelId: ChannelID, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { rateLimiter.executeRequest(endpoint: .pinnedMessage(channel: channelId, message: messageId), @@ -160,7 +160,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func editChannelPermission(_ permissionOverwrite: DiscordPermissionOverwrite, + func editChannelPermission(_ permissionOverwrite: DiscordPermissionOverwrite, on channelId: ChannelID, reason: String? = nil, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { @@ -179,7 +179,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getInvites(for channelId: ChannelID, + func getInvites(for channelId: ChannelID, callback: @escaping ([DiscordInvite], HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(invites)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -197,7 +197,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func editMessage(_ messageId: MessageID, + func editMessage(_ messageId: MessageID, on channelId: ChannelID, content: String, callback: ((DiscordMessage?, HTTPURLResponse?) -> ())? = nil) { @@ -219,7 +219,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getChannel(_ channelId: ChannelID, + func getChannel(_ channelId: ChannelID, callback: @escaping (DiscordChannel?, HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = {data, response, error in guard case let .object(channel)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -237,7 +237,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getMessages(for channelId: ChannelID, + func getMessages(for channelId: ChannelID, selection: DiscordEndpoint.Options.MessageSelection? = nil, limit: Int? = nil, callback: @escaping ([DiscordMessage], HTTPURLResponse?) -> ()) { @@ -266,7 +266,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getPinnedMessages(for channelId: ChannelID, + func getPinnedMessages(for channelId: ChannelID, callback: @escaping ([DiscordMessage], HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = {data, response, error in guard case let .array(messages)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -284,7 +284,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func modifyChannel(_ channelId: ChannelID, + func modifyChannel(_ channelId: ChannelID, options: [DiscordEndpoint.Options.ModifyChannel], reason: String? = nil, callback: ((DiscordGuildChannel?, HTTPURLResponse?) -> ())? = nil) { @@ -329,7 +329,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation. - public func sendMessage(_ message: DiscordMessage, + func sendMessage(_ message: DiscordMessage, to channelId: ChannelID, callback: ((DiscordMessage?, HTTPURLResponse?) -> ())? = nil) { let requestInfo: DiscordEndpoint.EndpointRequest @@ -372,7 +372,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { If all messages were sent successfully, the length of the array will be the same as the length of the input. Otherwise, the callback's array will be shorter. */ - public func sendMessages(_ messages: [DiscordMessage], + func sendMessages(_ messages: [DiscordMessage], to channelID: ChannelID, callback: (([DiscordMessage], HTTPURLResponse?) -> ())? = nil ) { guard let firstMessage = messages.first else { @@ -407,8 +407,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func triggerTyping(on channelId: ChannelID, - callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { + func triggerTyping(on channelId: ChannelID, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { rateLimiter.executeRequest(endpoint: .typing(channel: channelId), token: token, requestInfo: .post(content: nil, extraHeaders: nil), diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift index fbdac5bfa..967220481 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift @@ -22,7 +22,7 @@ import FoundationNetworking public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation - public func addGuildMemberRole(_ roleId: RoleID, + func addGuildMemberRole(_ roleId: RoleID, to userId: UserID, on guildId: GuildID, reason: String? = nil, @@ -40,7 +40,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func createGuildChannel(on guildId: GuildID, + func createGuildChannel(on guildId: GuildID, options: [DiscordEndpoint.Options.GuildCreateChannel], reason: String? = nil, callback: ((DiscordGuildChannel?, HTTPURLResponse?) -> ())? = nil) { @@ -85,7 +85,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func createGuildRole(on guildId: GuildID, + func createGuildRole(on guildId: GuildID, withOptions options: [DiscordEndpoint.Options.CreateRole] = [], reason: String? = nil, callback: @escaping (DiscordRole?, HTTPURLResponse?) -> ()) { @@ -133,7 +133,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func deleteGuild(_ guildId: GuildID, + func deleteGuild(_ guildId: GuildID, callback: ((DiscordGuild?, HTTPURLResponse?) -> ())? = nil) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(guild)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -193,7 +193,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getGuildBans(for guildId: GuildID, + func getGuildBans(for guildId: GuildID, callback: @escaping ([DiscordBan], HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(bans)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -213,7 +213,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getGuildChannels(_ guildId: GuildID, + func getGuildChannels(_ guildId: GuildID, callback: @escaping ([DiscordGuildChannel], HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(channels)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -232,7 +232,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getGuildMember(by id: UserID, + func getGuildMember(by id: UserID, on guildId: GuildID, callback: @escaping (DiscordGuildMember?, HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = { data, response, error in @@ -252,7 +252,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getGuildMembers(on guildId: GuildID, + func getGuildMembers(on guildId: GuildID, options: [DiscordEndpoint.Options.GuildGetMembers], callback: @escaping ([DiscordGuildMember], HTTPURLResponse?) -> ()) { var getParams: [String: String] = [:] @@ -285,7 +285,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getGuildRoles(for guildId: GuildID, + func getGuildRoles(for guildId: GuildID, callback: @escaping ([DiscordRole], HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(roles)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -304,7 +304,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func guildBan(userId: UserID, + func guildBan(userId: UserID, on guildId: GuildID, deleteMessageDays: Int = 7, reason: String? = nil, @@ -323,7 +323,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func modifyGuild(_ guildId: GuildID, + func modifyGuild(_ guildId: GuildID, options: [DiscordEndpoint.Options.ModifyGuild], reason: String? = nil, callback: ((DiscordGuild?, HTTPURLResponse?) -> ())? = nil) { @@ -376,7 +376,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func modifyGuildChannelPositions(on guildId: GuildID, + func modifyGuildChannelPositions(on guildId: GuildID, channelPositions: [[String: Any]], callback: (([DiscordGuildChannel], HTTPURLResponse?) -> ())? = nil) { guard let contentData = JSON.encodeJSONData(GenericEncodableArray(channelPositions)) else { return } @@ -431,7 +431,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func modifyGuildRole(_ role: DiscordRole, + func modifyGuildRole(_ role: DiscordRole, on guildId: GuildID, reason: String? = nil, callback: ((DiscordRole?, HTTPURLResponse?) -> ())? = nil) { @@ -459,7 +459,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func removeGuildBan(for userId: UserID, + func removeGuildBan(for userId: UserID, on guildId: GuildID, reason: String? = nil, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { @@ -478,7 +478,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation. - public func removeGuildMemberRole(_ roleId: RoleID, + func removeGuildMemberRole(_ roleId: RoleID, from userId: UserID, on guildId: GuildID, reason: String? = nil, @@ -496,7 +496,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func removeGuildRole(_ roleId: RoleID, + func removeGuildRole(_ roleId: RoleID, on guildId: GuildID, reason: String? = nil, callback: ((DiscordRole?, HTTPURLResponse?) -> ())? = nil) { diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Invites.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Invites.swift index 8db27fe7b..aeeb88b01 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Invites.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Invites.swift @@ -22,7 +22,7 @@ import FoundationNetworking public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation - public func acceptInvite(_ invite: String, + func acceptInvite(_ invite: String, callback: ((DiscordInvite?, HTTPURLResponse?) -> ())? = nil) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(invite)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -41,7 +41,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func deleteInvite(_ invite: String, + func deleteInvite(_ invite: String, reason: String? = nil, callback: ((DiscordInvite?, HTTPURLResponse?) -> ())? = nil) { var extraHeaders = [DiscordHeader: String]() @@ -67,7 +67,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getInvite(_ invite: String, + func getInvite(_ invite: String, callback: @escaping (DiscordInvite?, HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(invite)? = JSON.jsonFromResponse(data: data, response: response) else { diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift index 2f5c06679..dfb02680b 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift @@ -22,7 +22,7 @@ import FoundationNetworking public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation - public func createDM(with: UserID, + func createDM(with: UserID, callback: @escaping (DiscordDMChannel?, HTTPURLResponse?) -> ()) { guard let contentData = JSON.encodeJSONData(["recipient_id": with]) else { return } @@ -43,7 +43,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getDMs(callback: @escaping ([ChannelID: DiscordDMChannel], HTTPURLResponse?) -> ()) { + func getDMs(callback: @escaping ([ChannelID: DiscordDMChannel], HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(channels)? = JSON.jsonFromResponse(data: data, response: response) else { callback([:], response) @@ -62,7 +62,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getGuilds(callback: @escaping ([GuildID: DiscordUserGuild], HTTPURLResponse?) -> ()) { + func getGuilds(callback: @escaping ([GuildID: DiscordUserGuild], HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = {data, response, error in guard case let .array(guilds)? = JSON.jsonFromResponse(data: data, response: response) else { callback([:], response) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift index 04967e0ab..e10783a8f 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift @@ -22,7 +22,7 @@ import FoundationNetworking public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation - public func createWebhook(forChannel channelId: ChannelID, + func createWebhook(forChannel channelId: ChannelID, options: [DiscordEndpoint.Options.WebhookOption], reason: String? = nil, callback: @escaping (DiscordWebhook?, HTTPURLResponse?) -> () = {_, _ in }) { @@ -63,7 +63,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func deleteWebhook(_ webhookId: WebhookID, + func deleteWebhook(_ webhookId: WebhookID, reason: String? = nil, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { var extraHeaders = [DiscordHeader: String]() @@ -79,7 +79,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getWebhook(_ webhookId: WebhookID, + func getWebhook(_ webhookId: WebhookID, callback: @escaping (DiscordWebhook?, HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(webhook)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -98,7 +98,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getWebhooks(forChannel channelId: ChannelID, + func getWebhooks(forChannel channelId: ChannelID, callback: @escaping ([DiscordWebhook], HTTPURLResponse?) -> ()) { let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(webhooks)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -117,7 +117,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func getWebhooks(forGuild guildId: GuildID, + func getWebhooks(forGuild guildId: GuildID, callback: @escaping ([DiscordWebhook], HTTPURLResponse?) -> ()) { DefaultDiscordLogger.Logger.debug("Getting webhooks for guild: \(guildId)", type: "DiscordEndpointWebhooks") @@ -138,7 +138,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - public func modifyWebhook(_ webhookId: WebhookID, + func modifyWebhook(_ webhookId: WebhookID, options: [DiscordEndpoint.Options.WebhookOption], reason: String? = nil, callback: @escaping (DiscordWebhook?, HTTPURLResponse?) -> () = {_, _ in }) { diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift index 80a8f502b..5404e7413 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift @@ -609,7 +609,7 @@ public protocol DiscordEndpointConsumer { public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation - public func getBotURL(with permissions: DiscordPermission) -> URL? { + func getBotURL(with permissions: DiscordPermission) -> URL? { guard let user = self.user else { return nil } return DiscordOAuthEndpoint.createBotAddURL(for: user, with: permissions) diff --git a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift index cc2134b19..ab315e8cc 100644 --- a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift +++ b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift @@ -268,11 +268,6 @@ public struct DiscordRateLimitKey : Hashable { /// The list of parts that the URL contains public let urlParts: DiscordRateLimitURLParts - /// The hash of the key. - public var hashValue: Int { - return urlParts.rawValue &+ id.hashValue - } - // MARK: Initializers /// Creates a new endpoint key. @@ -285,6 +280,12 @@ public struct DiscordRateLimitKey : Hashable { public static func ==(lhs: DiscordRateLimitKey, rhs: DiscordRateLimitKey) -> Bool { return lhs.id == rhs.id && lhs.urlParts == rhs.urlParts } + + /// The hash of the key. + public func hash(into hasher: inout Hasher) { + hasher.combine(urlParts.rawValue) + hasher.combine(id) + } } /// A DiscordRateLimit's job is to keep track of a endpoint's rate limit. diff --git a/Sources/SwiftDiscord/Voice/DiscordOpusCoding.swift b/Sources/SwiftDiscord/Voice/DiscordOpusCoding.swift index 0bd19aad0..64e589b89 100644 --- a/Sources/SwiftDiscord/Voice/DiscordOpusCoding.swift +++ b/Sources/SwiftDiscord/Voice/DiscordOpusCoding.swift @@ -48,7 +48,7 @@ public extension DiscordOpusCodeable { /// - parameter assumingSize: The size of the frame, in number of samples per channel. /// - returns: The number of bytes in this frame. /// - public func maxFrameSize(assumingSize size: Int) -> Int { + func maxFrameSize(assumingSize size: Int) -> Int { return size * channels * MemoryLayout.size } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift index 5b617be8e..7f158d61d 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift @@ -419,7 +419,7 @@ public class DiscordEncoderMiddleware { middleware.standardOutput = pipe - ffmpeg.launchPath = "/usr/local/bin/ffmpeg" + ffmpeg.executableURL = URL(fileURLWithPath: "/usr/local/bin/ffmpeg") ffmpeg.standardInput = pipe ffmpeg.standardOutput = source.writeToHandler ffmpeg.arguments = ["-hide_banner", "-loglevel", "quiet", "-i", "pipe:0", "-f", "s16le", "-map", "0:a", @@ -437,9 +437,9 @@ public class DiscordEncoderMiddleware { /// /// Starts the middleware. /// - public func start() { - ffmpeg.launch() - middleware.launch() + public func start() throws { + try ffmpeg.run() + try middleware.run() } } #endif diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift index 42dda11c9..c46c5b593 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift @@ -224,8 +224,8 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { let packetSize = Int(crypto_secretbox_MACBYTES) + data.count let encrypted = UnsafeMutablePointer.allocate(capacity: packetSize) let rtpHeader = createRTPHeader() - var nonce = rtpHeader + DiscordVoiceEngine.padding - var buf = data + let nonce = rtpHeader + DiscordVoiceEngine.padding + let buf = data defer { encrypted.deallocate() } @@ -245,7 +245,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { guard audioSize > 0 else { throw EngineError.decryptionError } let unencrypted = UnsafeMutablePointer.allocate(capacity: audioSize) - var nonce = rtpHeader + DiscordVoiceEngine.padding + let nonce = rtpHeader + DiscordVoiceEngine.padding defer { unencrypted.deallocate() } @@ -655,7 +655,11 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { source.middleware = DiscordEncoderMiddleware(source: source, middleware: middleware, terminationHandler: terminationHandler) - source.middleware?.start() + do { + try source.middleware?.start() + } catch { + DefaultDiscordLogger.Logger.error("Could not start middleware: \(error)", type: DiscordVoiceEngine.logType) + } } #endif From 08f2cd04d4b1df16bc50d19b6102915333c80dd2 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 18 Oct 2019 23:36:28 +0200 Subject: [PATCH 014/104] Migrate to WebSocketKit Fix #83. Migrate to the new Vapor WebSocketKit library. --- Package.resolved | 24 +++++++++---------- Package.swift | 4 ++-- .../SwiftDiscord/Gateway/DiscordEngine.swift | 4 ++-- .../Gateway/DiscordEngineSpec.swift | 23 +++++++++--------- .../SwiftDiscord/Gateway/DiscordGateway.swift | 2 +- .../Voice/DiscordVoiceEngine.swift | 4 ++-- 6 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Package.resolved b/Package.resolved index b889d850d..e0fe1b60c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -11,30 +11,30 @@ } }, { - "package": "nio-websocket-client", - "repositoryURL": "https://github.com/vapor/nio-websocket-client", + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "f9a0955dff2b6a7a466cc26db2d061b682023197", - "version": null + "revision": "9201908b54578aa33f1d1826a5a680aca8991843", + "version": "2.8.0" } }, { - "package": "swift-nio", - "repositoryURL": "https://github.com/apple/swift-nio.git", + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "32760eae40e6b7cb81d4d543bb0a9f548356d9a2", - "version": "2.7.1" + "revision": "bd235c580bd4e76beeefdefd86b86e08bd267d49", + "version": "2.4.2" } }, { - "package": "swift-nio-ssl", - "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", + "package": "websocket-kit", + "repositoryURL": "https://github.com/vapor/websocket-kit", "state": { "branch": null, - "revision": "f5dd7a60ff56f501ff7bf9be753e4b1875bfaf20", - "version": "2.4.0" + "revision": "d2fbfc28cb08e7a41644a92da16a383c8f9cc6f4", + "version": null } } ] diff --git a/Package.swift b/Package.swift index b843e6f3c..420ba3e86 100644 --- a/Package.swift +++ b/Package.swift @@ -20,11 +20,11 @@ import PackageDescription var deps: [Package.Dependency] = [ - .package(url: "https://github.com/vapor/nio-websocket-client", .revision("f9a0955dff2b6a7a466cc26db2d061b682023197")), + .package(url: "https://github.com/vapor/websocket-kit", .revision("d2fbfc28cb08e7a41644a92da16a383c8f9cc6f4")), .package(url: "https://github.com/IBM-Swift/BlueSocket", .upToNextMinor(from: "1.0.0")) ] -var targetDeps: [Target.Dependency] = ["AsyncWebSocketClient", "COPUS", "Sodium", "Socket"] +var targetDeps: [Target.Dependency] = ["WebSocketKit", "COPUS", "Sodium", "Socket"] let package = Package( name: "SwiftDiscord", diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift index d24e1f4c8..1b72d2a12 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift @@ -17,7 +17,7 @@ import Foundation import NIO -import AsyncWebSocketClient +import WebSocketKit import Dispatch #if os(macOS) @@ -99,7 +99,7 @@ open class DiscordEngine : DiscordEngineSpec { public var sessionId: String? /// The underlying WebSocket. - public var websocket: WebSocketClient.Socket? + public var websocket: WebSocket? /// Whether this engine is connected to the gateway. public internal(set) var connected = false diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index 8115c3356..2a3ad7aad 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -18,7 +18,7 @@ import Dispatch import Foundation import NIO -import AsyncWebSocketClient +import WebSocketKit /// Declares that a type will be an Engine for the Discord Gateway. public protocol DiscordEngineSpec : DiscordShard { @@ -48,7 +48,7 @@ public protocol DiscordWebSocketable : AnyObject { var parseQueue: DispatchQueue { get } /// A reference to the underlying WebSocket. - var websocket: WebSocketClient.Socket? { get set } + var websocket: WebSocket? { get set } // MARK: Methods @@ -87,20 +87,20 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu this.parseGatewayMessage(text) } + + websocket?.onClose.whenSuccess { [weak self] in + guard let this = self else { return } + + DefaultDiscordLogger.Logger.log("Websocket closed, \(this.description)", type: "DiscordWebSocketable") + } - websocket?.onCloseCode { [weak self] code in + websocket?.onClose.whenFailure { [weak self] err in guard let this = self else { return } - DefaultDiscordLogger.Logger.log("WebSocket closed, code: \(code), \(this.description);", type: "DiscordWebSocketable") + DefaultDiscordLogger.Logger.log("WebSocket errored: \(err), \(this.description);", type: "DiscordWebSocketable") this.handleClose(reason: nil) } - - websocket?.onError { [weak self] ws, err in - guard let this = self else { return } - - DefaultDiscordLogger.Logger.log("WebSocket errored, \(err), \(this.description)", type: "DiscordWebSocketable") - } } /// @@ -121,9 +121,10 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu maxFrameSize: 1 << 31 )) let future = wsClient.connect( + scheme: url.scheme!, host: url.host!, port: url.port ?? 443, - uri: path + path: path ) { [weak self] ws in guard let this = self else { return } diff --git a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift index c66eda297..611894063 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift @@ -100,7 +100,7 @@ public extension DiscordGatewayable where Self: DiscordWebSocketable & DiscordRu DefaultDiscordLogger.Logger.debug("Sending ws: \(payloadString)", type: description) runloop.execute { - self.websocket?.send(text: payloadString) + self.websocket?.send(payloadString) } } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift index c46c5b593..6b23ea954 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift @@ -17,7 +17,7 @@ import Foundation import Dispatch -import AsyncWebSocketClient +import WebSocketKit import Socket import Sodium @@ -86,7 +86,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { } /// The underlying websocket. - public var websocket: WebSocketClient.Socket? + public var websocket: WebSocket? /// The voice engine's delegate. public private(set) weak var voiceDelegate: DiscordVoiceEngineDelegate? From e5fec9034fd0b7091b1524e86c49513406d992b3 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 18 Oct 2019 23:50:30 +0200 Subject: [PATCH 015/104] Handle successful web socket close --- Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index 2a3ad7aad..8b6131754 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -92,6 +92,8 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu guard let this = self else { return } DefaultDiscordLogger.Logger.log("Websocket closed, \(this.description)", type: "DiscordWebSocketable") + + this.handleClose(reason: nil) } websocket?.onClose.whenFailure { [weak self] err in From 247e3424dfb2dd9743db361ad6d45d17c6490881 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 19 Oct 2019 00:03:36 +0200 Subject: [PATCH 016/104] Use WebSocket.connect instead of WebSocketClient --- .../Gateway/DiscordEngineSpec.swift | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index 8b6131754..487de4406 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -118,15 +118,17 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu let url = URL(string: connectURL)! let path = url.path.isEmpty ? "/" : url.path - let wsClient = WebSocketClient(eventLoopGroupProvider: .shared(runloop), configuration: .init( - tlsConfiguration: .clientDefault, - maxFrameSize: 1 << 31 - )) - let future = wsClient.connect( - scheme: url.scheme!, - host: url.host!, - port: url.port ?? 443, - path: path + + let future = WebSocket.connect( + scheme: url.scheme ?? "wss", + host: url.host!, + port: url.port ?? 443, + path: path, + configuration: .init( + tlsConfiguration: .clientDefault, + maxFrameSize: 1 << 31 + ), + on: runloop ) { [weak self] ws in guard let this = self else { return } From 6f8503520e028cae17e06efd53f60b04585414a2 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 19 Oct 2019 00:47:27 +0200 Subject: [PATCH 017/104] Add fallbacks for deprecated Process methods on macOS Use #available and #if os checks to ensure that Process.run and .executableURL are available. If not, use the older methods. --- .../Voice/DiscordVoiceDataSource.swift | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift index 7f158d61d..192e189ab 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift @@ -418,8 +418,20 @@ public class DiscordEncoderMiddleware { pipe = Pipe() middleware.standardOutput = pipe + + let ffmpegPath = "/usr/local/bin/ffmpeg" + let ffmpegURL = URL(fileURLWithPath: ffmpegPath) + + pathSetter: do { + #if os(macOS) + guard #available(macOS 10.13, *) else { + ffmpeg.launchPath = ffmpegPath + break pathSetter + } + #endif + ffmpeg.executableURL = ffmpegURL + } - ffmpeg.executableURL = URL(fileURLWithPath: "/usr/local/bin/ffmpeg") ffmpeg.standardInput = pipe ffmpeg.standardOutput = source.writeToHandler ffmpeg.arguments = ["-hide_banner", "-loglevel", "quiet", "-i", "pipe:0", "-f", "s16le", "-map", "0:a", @@ -433,11 +445,19 @@ public class DiscordEncoderMiddleware { source?.finishUpAndClose() } } - + /// /// Starts the middleware. /// public func start() throws { + #if os(macOS) + guard #available(macOS 10.13, *) else { + ffmpeg.launch() + middleware.launch() + return + } + #endif + try ffmpeg.run() try middleware.run() } From d6bcc60eaca9e0bcef9f6d08c3a60ab6abac5ad0 Mon Sep 17 00:00:00 2001 From: Tellow Krinkle Date: Sun, 1 Sep 2019 23:24:58 -0500 Subject: [PATCH 018/104] Fix libsodium import --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 420ba3e86..47a7dfc03 100644 --- a/Package.swift +++ b/Package.swift @@ -35,7 +35,7 @@ let package = Package( targets: [ .target(name: "SwiftDiscord", dependencies: targetDeps), .systemLibrary(name: "COPUS", pkgConfig: "opus"), - .systemLibrary(name: "Sodium"), + .systemLibrary(name: "Sodium", pkgConfig: "libsodium"), .testTarget(name: "SwiftDiscordTests", dependencies: ["SwiftDiscord"]), ] ) From b45727bd23549a6f243fda3ca7d3702c5fd5d15b Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 10:57:39 -0500 Subject: [PATCH 019/104] Swift 5.1 --- .travis.yml | 2 +- Package.swift | 2 +- Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17ef7c0f7..d515c99a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ matrix: sudo: required language: generic - os: osx - osx_image: xcode9.3 + osx_image: xcode11 language: objective-c branches: only: diff --git a/Package.swift b/Package.swift index 89f9aa9ae..cdf2a34f3 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.2 +// swift-tools-version:5.1 // The MIT License (MIT) // Copyright (c) 2016 Erik Little diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift index 6b23ea954..14ade3889 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift @@ -292,7 +292,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { do { var data = Data() - try udpSocket.write(from: Data(bytes: discoveryData)) + try udpSocket.write(from: Data(discoveryData)) _ = try udpSocket.readDatagram(into: &data) let (ip, port) = try self.extractIPAndPort(from: data) @@ -610,7 +610,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { type: DiscordVoiceEngine.logType) do { - try udpSocket.write(from: Data(bytes: createVoicePacket(data))) + try udpSocket.write(from: Data(createVoicePacket(data))) } catch EngineError.encryptionError { error(message: "Error encrypting packet") } catch let err { From fcf87ebcb73e85a8c1e8f2df9510a9e88804ffdc Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 11:42:20 -0500 Subject: [PATCH 020/104] fix tests --- Package.resolved | 2 +- Package.swift | 2 +- Tests/SwiftDiscordTests/TestDiscordEngine.swift | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index 783d1758a..5425702bd 100644 --- a/Package.resolved +++ b/Package.resolved @@ -51,7 +51,7 @@ "repositoryURL": "https://github.com/vapor/websocket-kit", "state": { "branch": null, - "revision": "d2fbfc28cb08e7a41644a92da16a383c8f9cc6f4", + "revision": "66c0ea58398f055b5a0d92b0d5f4c32ef0c02eeb", "version": null } } diff --git a/Package.swift b/Package.swift index cdf2a34f3..13e1c32a5 100644 --- a/Package.swift +++ b/Package.swift @@ -20,7 +20,7 @@ import PackageDescription var deps: [Package.Dependency] = [ - .package(url: "https://github.com/vapor/websocket-kit", .revision("d2fbfc28cb08e7a41644a92da16a383c8f9cc6f4")), + .package(url: "https://github.com/vapor/websocket-kit", .revision("66c0ea58398f055b5a0d92b0d5f4c32ef0c02eeb")), .package(url: "https://github.com/IBM-Swift/BlueSocket", .upToNextMinor(from: "1.0.0")), .package(url: "https://github.com/nuclearace/copus", .upToNextMinor(from: "2.1.1")), .package(url: "https://github.com/nuclearace/Sodium", .upToNextMinor(from: "2.0.0")), diff --git a/Tests/SwiftDiscordTests/TestDiscordEngine.swift b/Tests/SwiftDiscordTests/TestDiscordEngine.swift index 0f90d39a9..d97e3c6d9 100644 --- a/Tests/SwiftDiscordTests/TestDiscordEngine.swift +++ b/Tests/SwiftDiscordTests/TestDiscordEngine.swift @@ -4,6 +4,7 @@ import Foundation import XCTest +import NIO @testable import SwiftDiscord public class TestDiscordEngine : XCTestCase, DiscordShardDelegate { @@ -39,6 +40,7 @@ public class TestDiscordEngine : XCTestCase, DiscordShardDelegate { var engine: DiscordEngine! var expectation: XCTestExpectation! + var loop: MultiThreadedEventLoopGroup! public static var allTests: [(String, (TestDiscordEngine) -> () -> ())] { return [ @@ -49,7 +51,8 @@ public class TestDiscordEngine : XCTestCase, DiscordShardDelegate { } public override func setUp() { - engine = DiscordEngine(delegate: self) + loop = MultiThreadedEventLoopGroup(numberOfThreads: 1) + engine = DiscordEngine(delegate: self, onLoop: loop.next()) } } From 9ac6a30fda5ce5b2fdb83217a51e5aa26869de86 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 12:10:52 -0500 Subject: [PATCH 021/104] Try using swiftenv on linux for installing swift --- .travis/before_install.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 6b54db2c1..03802aaed 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -7,9 +7,17 @@ if [[ $TRAVIS_OS_NAME == 'osx' ]]; then brew install libsodium brew install opus else - # Install Swift, Vapor and Opus + git clone https://github.com/kylef/swiftenv.git ~/.swiftenv + echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile + echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile + echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile + + reload + swiftenv install 5.1 && swiftenv global 5.1 + + # Install Vapor and Opus eval "$(curl -sL https://apt.vapor.sh)" - sudo apt-get install swift vapor libopus-dev + sudo apt-get install vapor libopus-dev # Sodium wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.16.tar.gz From 1f6dcd6e7ae1e60a3251068f59ee47c72114be60 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 12:13:57 -0500 Subject: [PATCH 022/104] wrong command --- .travis/before_install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 03802aaed..22f689881 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -10,10 +10,10 @@ else git clone https://github.com/kylef/swiftenv.git ~/.swiftenv echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile - echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile - reload - swiftenv install 5.1 && swiftenv global 5.1 + source ~/.bash_profile + + echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile # Install Vapor and Opus eval "$(curl -sL https://apt.vapor.sh)" From afbbcc4bb8146f26f4c228ededea3c0f578e114b Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 12:17:01 -0500 Subject: [PATCH 023/104] actually install swift --- .travis/before_install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 22f689881..8e7eaa2c2 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -15,6 +15,8 @@ else echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile + swiftenv install 5.1 && swiftenv global 5.1 + # Install Vapor and Opus eval "$(curl -sL https://apt.vapor.sh)" sudo apt-get install vapor libopus-dev From 96bf60a1061f1c44cd580c2f4668ac5c40eb3248 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 12:21:52 -0500 Subject: [PATCH 024/104] install over vapor swift --- .travis/before_install.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 8e7eaa2c2..363a358da 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -10,17 +10,16 @@ else git clone https://github.com/kylef/swiftenv.git ~/.swiftenv echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile - source ~/.bash_profile - echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile - swiftenv install 5.1 && swiftenv global 5.1 - # Install Vapor and Opus eval "$(curl -sL https://apt.vapor.sh)" sudo apt-get install vapor libopus-dev + // Swift + swiftenv install 5.1 && swiftenv global 5.1 + # Sodium wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.16.tar.gz tar -xzf libsodium-1.0.16.tar.gz From 2eefc6fa526418997b123432e4760dc1d8fa95c1 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 12:24:44 -0500 Subject: [PATCH 025/104] fix comment --- .travis/before_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 363a358da..747e8bf3d 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -17,7 +17,7 @@ else eval "$(curl -sL https://apt.vapor.sh)" sudo apt-get install vapor libopus-dev - // Swift + # Swift swiftenv install 5.1 && swiftenv global 5.1 # Sodium From 3fb14e9fec76b2624d50d6af34f3108c63c37f7d Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 12:31:10 -0500 Subject: [PATCH 026/104] uninstall vapor swift --- .travis/before_install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 747e8bf3d..35300eabe 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -18,6 +18,7 @@ else sudo apt-get install vapor libopus-dev # Swift + sudo apt-get remove swift swiftenv install 5.1 && swiftenv global 5.1 # Sodium From 93225c3f8fe2af122ed31ef7a69a1341e45fa316 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 12:42:09 -0500 Subject: [PATCH 027/104] more linux fiddling --- .travis/before_install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 35300eabe..d003d600e 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -10,16 +10,16 @@ else git clone https://github.com/kylef/swiftenv.git ~/.swiftenv echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile - source ~/.bash_profile echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile + source ~/.bash_profile # Install Vapor and Opus eval "$(curl -sL https://apt.vapor.sh)" sudo apt-get install vapor libopus-dev # Swift - sudo apt-get remove swift - swiftenv install 5.1 && swiftenv global 5.1 + ~/.swiftenv/bin/swiftenv install 5.1 + ~/.swiftenv/bin/swiftenv global 5.1 # Sodium wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.16.tar.gz From efef26ad7babb99d99cb56beffb7d36a1480f736 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 12:52:14 -0500 Subject: [PATCH 028/104] more linux fiddling --- .travis/before_install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index d003d600e..1ab4995c0 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -18,8 +18,8 @@ else sudo apt-get install vapor libopus-dev # Swift - ~/.swiftenv/bin/swiftenv install 5.1 - ~/.swiftenv/bin/swiftenv global 5.1 + swiftenv install 5.1 && swiftenv global 5.1 + swift --version # Sodium wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.16.tar.gz From 976689a7d54215643892e5284a8937a306829819 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 12:58:35 -0500 Subject: [PATCH 029/104] try run.sh --- .travis/before_install.sh | 5 ++--- .travis/run.sh | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 1ab4995c0..851eb463e 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -13,13 +13,12 @@ else echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile source ~/.bash_profile - # Install Vapor and Opus + # Install Vapor and Opus eval "$(curl -sL https://apt.vapor.sh)" sudo apt-get install vapor libopus-dev # Swift - swiftenv install 5.1 && swiftenv global 5.1 - swift --version + swiftenv install 5.1 # Sodium wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.16.tar.gz diff --git a/.travis/run.sh b/.travis/run.sh index 2c34bcd81..ea22ebad6 100755 --- a/.travis/run.sh +++ b/.travis/run.sh @@ -1,7 +1,9 @@ #!/bin/bash if [[ $TRAVIS_OS_NAME == 'osx' ]]; then - swift test -Xlinker -L/usr/local/lib -Xlinker -lopus -Xcc -I/usr/local/include + swift test else - swift test -Xlinker -L/usr/lib -Xlinker -lopus -Xcc -I/usr/include + swiftenv global 5.1 + swift --version + swift test fi From 3168bd6a2c3a1c54502175854021bbebaefd40c6 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 13:04:07 -0500 Subject: [PATCH 030/104] more fiddling --- .travis/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/run.sh b/.travis/run.sh index ea22ebad6..8f6eb23dd 100755 --- a/.travis/run.sh +++ b/.travis/run.sh @@ -3,7 +3,7 @@ if [[ $TRAVIS_OS_NAME == 'osx' ]]; then swift test else - swiftenv global 5.1 + ~/.swiftenv/bin/swiftenv global 5.1 swift --version swift test fi From e3a16c5eaeb63ccf9d718a8b70cc9e58d4f60852 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 13:07:51 -0500 Subject: [PATCH 031/104] use boinic --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d515c99a4..4f2c733e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ matrix: include: - os: linux - dist: trusty + dist: bionic sudo: required language: generic - os: osx From 3e58750716d05b6eb37efd2d8b03bb2f613d69dc Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 13:14:43 -0500 Subject: [PATCH 032/104] try installing swift in run.sh --- .travis/before_install.sh | 9 --------- .travis/run.sh | 9 ++++++++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.travis/before_install.sh b/.travis/before_install.sh index 851eb463e..7c3beb614 100755 --- a/.travis/before_install.sh +++ b/.travis/before_install.sh @@ -7,19 +7,10 @@ if [[ $TRAVIS_OS_NAME == 'osx' ]]; then brew install libsodium brew install opus else - git clone https://github.com/kylef/swiftenv.git ~/.swiftenv - echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile - echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile - echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile - source ~/.bash_profile - # Install Vapor and Opus eval "$(curl -sL https://apt.vapor.sh)" sudo apt-get install vapor libopus-dev - # Swift - swiftenv install 5.1 - # Sodium wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.16.tar.gz tar -xzf libsodium-1.0.16.tar.gz diff --git a/.travis/run.sh b/.travis/run.sh index 8f6eb23dd..8c24eb7a9 100755 --- a/.travis/run.sh +++ b/.travis/run.sh @@ -3,7 +3,14 @@ if [[ $TRAVIS_OS_NAME == 'osx' ]]; then swift test else - ~/.swiftenv/bin/swiftenv global 5.1 + git clone https://github.com/kylef/swiftenv.git ~/.swiftenv + echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile + echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile + echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile + source ~/.bash_profile + + # Swift + swiftenv install 5.1 && swiftenv global 5.1 swift --version swift test fi From af82a80a6d79f361b720e99c08e111bb01d852a6 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 15:21:20 -0500 Subject: [PATCH 033/104] Fix sending speaking packet --- Sources/SwiftDiscord/DiscordLogger.swift | 17 ++++++++--- .../Voice/DiscordVoiceDataSource.swift | 2 +- .../Voice/DiscordVoiceEngine.swift | 30 +++++++++++-------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/Sources/SwiftDiscord/DiscordLogger.swift b/Sources/SwiftDiscord/DiscordLogger.swift index 296766a48..49fa58aa9 100644 --- a/Sources/SwiftDiscord/DiscordLogger.swift +++ b/Sources/SwiftDiscord/DiscordLogger.swift @@ -25,8 +25,10 @@ public enum DiscordLogLevel { case info /// Log content of events. case verbose - /// Log everything. + /// Log almost everything, minus the noisiest things. case debug + /// Log everything. + case trace } /// Declares that a type will act as a logger. @@ -54,21 +56,21 @@ public protocol DiscordLogger { public extension DiscordLogger { /// Normal log messages. func log(_ message: @autoclosure () -> String, type: String) { - guard level == .info || level == .verbose || level == .debug else { return } + guard level == .info || level == .verbose || level == .debug || level == .trace else { return } abstractLog("LOG", message: message(), type: type) } /// More info on log messages. func verbose(_ message: @autoclosure () -> String, type: String) { - guard level == .verbose || level == .debug else { return } + guard level == .verbose || level == .debug || level == .trace else { return } abstractLog("VERBOSE", message: message(), type: type) } /// Debug messages. func debug(_ message: @autoclosure () -> String, type: String) { - guard level == .debug else { return } + guard level == .debug || level == .trace else { return } abstractLog("DEBUG", message: message(), type: type) } @@ -78,6 +80,13 @@ public extension DiscordLogger { abstractLog("ERROR", message: message(), type: type) } + /// trace Messages. + func trace(_ message: @autoclosure () -> String, type: String) { + guard level == .trace else { return } + + abstractLog("TRACE", message: message(), type: type) + } + private func abstractLog(_ logType: String, message: String, type: String) { NSLog("\(logType): \(type): \(message)") } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift index 192e189ab..7ee7780c9 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift @@ -185,7 +185,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { encoderQueue.sync { done = self.done - DefaultDiscordLogger.Logger.debug("Buffer state: count: \(self.readBuffer.count) drain: \(self.drain)", + DefaultDiscordLogger.Logger.trace("Buffer state: count: \(self.readBuffer.count) drain: \(self.drain)", type: DiscordBufferedVoiceDataSource.logType) if self.drain && self.readBuffer.count <= self.drainThreshold { diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift index 14ade3889..337f00ed4 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift @@ -54,7 +54,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// The voice url public var connectURL: String { - return "wss://\(voiceServerInformation.endpoint.components(separatedBy: ":")[0])?v=3" + return "wss://\(voiceServerInformation.endpoint.components(separatedBy: ":")[0])?v=4" } /// The connect UUID of this WebSocketable. @@ -109,7 +109,10 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// The UDP socket that is used to send/receive voice data public private(set) var udpSocket: Socket? - /// Our UDP port + // Server UDP ip + public private(set) var udpIp = "" + + /// Server UDP port public private(set) var udpPort = -1 /// Information about the voice server we are connected to @@ -287,7 +290,6 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { udpQueueWrite.async { guard let udpSocket = self.udpSocket else { return } - // print("Finding IP") let discoveryData = [UInt8](repeating: 0x00, count: 70) do { @@ -327,7 +329,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { do { sendVoiceData(try source.engineNeedsData(self)) } catch DiscordVoiceDataSourceStatus.noData { - DefaultDiscordLogger.Logger.debug("No data", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.Logger.trace("No data", type: DiscordVoiceEngine.logType) if speaking { sendSpeaking(false) @@ -429,7 +431,8 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { guard case let .object(voiceInformation) = payload, let ssrc = voiceInformation["ssrc"] as? Int, let udpPort = voiceInformation["port"] as? Int, - let modes = voiceInformation["modes"] as? [String] else { + let modes = voiceInformation["modes"] as? [String], + let ip = voiceInformation["ip"] as? String else { disconnect() return @@ -438,6 +441,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { self.udpPort = udpPort self.modes = modes self.ssrc = UInt32(ssrc) + self.udpIp = ip startUDP() } @@ -585,8 +589,9 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { self.speaking = speaking let speakingObject: [String: Any] = [ - "speaking": speaking, - "delay": 0 + "speaking": 1 << 1, + "delay": 0, + "ssrc": ssrc ] sendPayload(DiscordGatewayPayload(code: .voice(.speaking), payload: .object(speakingObject))) @@ -606,7 +611,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { sendSpeaking(true) } - DefaultDiscordLogger.Logger.debug("Should send voice data: \(data.count) bytes", + DefaultDiscordLogger.Logger.trace("Should send voice data: \(data.count) bytes", type: DiscordVoiceEngine.logType) do { @@ -686,9 +691,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { } private func startUDP() { - guard udpPort != -1 else { return } - - let base = voiceServerInformation.endpoint.components(separatedBy: ":")[0] + guard udpPort != -1, !udpIp.isEmpty else { return } DefaultDiscordLogger.Logger.debug("Starting voice UDP connection", type: DiscordVoiceEngine.logType) @@ -697,7 +700,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { protocolFamily: .inet, socketType: .datagram, proto: .udp, - hostname: base, + hostname: "\(udpIp)", port: Int32(udpPort) ) else { throw EngineError.unknown @@ -707,8 +710,9 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { // Begin async UDP setup findIP() - } catch { + } catch let err { // TODO Handle voice error disconnect from voice + DefaultDiscordLogger.Logger.debug("Voice error \(err)", type: DiscordVoiceEngine.logType) } } } From 4f8f528f5334abdfb9104101e0d2eba488f8e8fb Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 1 Dec 2019 15:23:23 -0500 Subject: [PATCH 034/104] use error log --- Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift index 337f00ed4..1d0e2dccc 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift @@ -712,7 +712,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { findIP() } catch let err { // TODO Handle voice error disconnect from voice - DefaultDiscordLogger.Logger.debug("Voice error \(err)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.Logger.error("UDP setup error \(err)", type: DiscordVoiceEngine.logType) } } } From 93fc7ab03ec90a6ffe36310b426ae5fac868d94a Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 4 Jan 2020 03:20:34 +0100 Subject: [PATCH 035/104] Add swift-log --- Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 13e1c32a5..a63849c32 100644 --- a/Package.swift +++ b/Package.swift @@ -24,9 +24,10 @@ var deps: [Package.Dependency] = [ .package(url: "https://github.com/IBM-Swift/BlueSocket", .upToNextMinor(from: "1.0.0")), .package(url: "https://github.com/nuclearace/copus", .upToNextMinor(from: "2.1.1")), .package(url: "https://github.com/nuclearace/Sodium", .upToNextMinor(from: "2.0.0")), + .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), ] -var targetDeps: [Target.Dependency] = ["WebSocketKit", "COPUS", "Sodium", "Socket"] +var targetDeps: [Target.Dependency] = ["WebSocketKit", "COPUS", "Sodium", "Socket", "Logging"] let package = Package( name: "SwiftDiscord", From f2e23715e31c2e2ff3f0e40735e36a985c09ae9d Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 4 Jan 2020 03:38:59 +0100 Subject: [PATCH 036/104] Change logging backend from NSLog to swift-log --- Package.resolved | 9 ++ .../SwiftDiscord/Channel/DiscordChannel.swift | 2 +- Sources/SwiftDiscord/DiscordClient.swift | 96 +++++++++---------- Sources/SwiftDiscord/DiscordJSON.swift | 14 +-- Sources/SwiftDiscord/DiscordLogger.swift | 53 +++++----- .../SwiftDiscord/Gateway/DiscordEngine.swift | 32 +++---- .../Gateway/DiscordEngineSpec.swift | 18 ++-- .../SwiftDiscord/Gateway/DiscordGateway.swift | 2 +- .../Gateway/DiscordSharding.swift | 6 +- Sources/SwiftDiscord/Guild/DiscordEmoji.swift | 2 +- Sources/SwiftDiscord/Guild/DiscordGuild.swift | 10 +- .../Guild/DiscordGuildChannel.swift | 2 +- .../Guild/DiscordGuildMember.swift | 2 +- .../SwiftDiscord/Rest/DiscordEndpoint.swift | 4 +- .../DiscordEndpointConsumer+Channels.swift | 4 +- .../Rest/DiscordEndpointConsumer+Guilds.swift | 14 +-- .../Rest/DiscordEndpointConsumer+User.swift | 2 +- .../DiscordEndpointConsumer+Webhooks.swift | 6 +- .../Rest/DiscordRateLimiter.swift | 14 +-- .../Voice/DiscordVoiceDataSource.swift | 26 ++--- .../Voice/DiscordVoiceDecoder.swift | 8 +- .../Voice/DiscordVoiceEngine.swift | 54 +++++------ .../Voice/DiscordVoiceManager.swift | 8 +- 23 files changed, 196 insertions(+), 192 deletions(-) diff --git a/Package.resolved b/Package.resolved index 5425702bd..615d695ae 100644 --- a/Package.resolved +++ b/Package.resolved @@ -28,6 +28,15 @@ "version": "2.0.0" } }, + { + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", + "state": { + "branch": null, + "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", + "version": "1.2.0" + } + }, { "package": "swift-nio", "repositoryURL": "https://github.com/apple/swift-nio.git", diff --git a/Sources/SwiftDiscord/Channel/DiscordChannel.swift b/Sources/SwiftDiscord/Channel/DiscordChannel.swift index ce91c6b6d..b8bf92b22 100644 --- a/Sources/SwiftDiscord/Channel/DiscordChannel.swift +++ b/Sources/SwiftDiscord/Channel/DiscordChannel.swift @@ -72,7 +72,7 @@ public extension DiscordChannel { func delete(reason: String? = nil) { guard let client = self.client else { return } - DefaultDiscordLogger.Logger.log("Deleting channel: \(id)", type: "DiscordChannel") + DefaultDiscordLogger.logger.log("Deleting channel: \(id)", type: "DiscordChannel") client.deleteChannel(id, reason: reason) } diff --git a/Sources/SwiftDiscord/DiscordClient.swift b/Sources/SwiftDiscord/DiscordClient.swift index b9bc1a605..0868f7efb 100644 --- a/Sources/SwiftDiscord/DiscordClient.swift +++ b/Sources/SwiftDiscord/DiscordClient.swift @@ -117,9 +117,9 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco case let .handleQueue(queue): handleQueue = queue case let .log(level): - DefaultDiscordLogger.Logger.level = level + DefaultDiscordLogger.logger.level = level case let .logger(logger): - DefaultDiscordLogger.Logger = logger + DefaultDiscordLogger.logger = logger case let .rateLimiter(limiter): self.rateLimiter = limiter case let .shardingInfo(shardingInfo): @@ -151,7 +151,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// with the client. /// open func connect() { - DefaultDiscordLogger.Logger.log("Connecting", type: logType) + DefaultDiscordLogger.logger.log("Connecting", type: logType) shardManager.manuallyShatter(withInfo: shardingInfo) @@ -164,7 +164,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// Calling this method turns off automatic resuming, set `resume` to `true` before calling `connect()` again. /// open func disconnect() { - DefaultDiscordLogger.Logger.log("Disconnecting", type: logType) + DefaultDiscordLogger.logger.log("Disconnecting", type: logType) connected = false @@ -183,7 +183,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// public func findChannel(fromId channelId: ChannelID) -> DiscordChannel? { if let channel = channelCache[channelId] { - DefaultDiscordLogger.Logger.debug("Got cached channel \(channel)", type: logType) + DefaultDiscordLogger.logger.debug("Got cached channel \(channel)", type: logType) return channel } @@ -195,14 +195,14 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco } else if let dmChannel = directChannels[channelId] { channel = dmChannel } else { - DefaultDiscordLogger.Logger.debug("Couldn't find channel \(channelId)", type: logType) + DefaultDiscordLogger.logger.debug("Couldn't find channel \(channelId)", type: logType) return nil } channelCache[channel.id] = channel - DefaultDiscordLogger.Logger.debug("Found channel \(channel)", type: logType) + DefaultDiscordLogger.logger.debug("Found channel \(channel)", type: logType) return channel } @@ -217,7 +217,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// open func handleDispatch(event: DiscordDispatchEvent, data: DiscordGatewayPayloadData) { guard case let .object(eventData) = data else { - DefaultDiscordLogger.Logger.error("Got dispatch event without an object: \(event), \(data)", + DefaultDiscordLogger.logger.error("Got dispatch event without an object: \(event), \(data)", type: "DiscordDispatchEventHandler") return } @@ -268,7 +268,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco return } - DefaultDiscordLogger.Logger.log("Joining voice channel: \(channel)", type: self.logType) + DefaultDiscordLogger.logger.log("Joining voice channel: \(channel)", type: self.logType) shardManager.sendPayload(DiscordGatewayPayload(code: .gateway(.voiceStatusUpdate), payload: .object(["guild_id": String(describing: guild.id), @@ -285,7 +285,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter onGuild: The snowflake of the guild that you want to leave. /// open func leaveVoiceChannel(onGuild guildId: GuildID) { - DefaultDiscordLogger.Logger.log("Leaving voice channel on guild: \(guildId)", type: logType) + DefaultDiscordLogger.logger.log("Leaving voice channel on guild: \(guildId)", type: logType) voiceManager.leaveVoiceChannel(onGuild: guildId) } @@ -455,7 +455,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleChannelCreate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling channel create", type: logType) + DefaultDiscordLogger.logger.log("Handling channel create", type: logType) guard let channel = channelFromObject(data, withClient: self) else { return } @@ -470,7 +470,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco break } - DefaultDiscordLogger.Logger.verbose("Created channel: \(channel)", type: logType) + DefaultDiscordLogger.logger.verbose("Created channel: \(channel)", type: logType) delegate?.client(self, didCreateChannel: channel) } @@ -485,7 +485,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleChannelDelete(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling channel delete", type: logType) + DefaultDiscordLogger.logger.log("Handling channel delete", type: logType) guard let type = DiscordChannelType(rawValue: data["type"] as? Int ?? -1) else { return } guard let channelId = Snowflake(data["id"] as? String) else { return } @@ -504,7 +504,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco channelCache.removeValue(forKey: channelId) - DefaultDiscordLogger.Logger.verbose("Removed channel: \(removedChannel)", type: logType) + DefaultDiscordLogger.logger.verbose("Removed channel: \(removedChannel)", type: logType) delegate?.client(self, didDeleteChannel: removedChannel) } @@ -519,13 +519,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleChannelUpdate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling channel update", type: logType) + DefaultDiscordLogger.logger.log("Handling channel update", type: logType) guard let channel = guildChannel(fromObject: data, guildID: nil, client: self) else { return } - DefaultDiscordLogger.Logger.verbose("Updated channel: \(channel)", type: logType) + DefaultDiscordLogger.logger.verbose("Updated channel: \(channel)", type: logType) guilds[channel.guildId]?.channels[channel.id] = channel @@ -544,11 +544,11 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildCreate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild create", type: logType) + DefaultDiscordLogger.logger.log("Handling guild create", type: logType) let guild = DiscordGuild(guildObject: data, client: self) - DefaultDiscordLogger.Logger.verbose("Created guild: \(guild)", type: self.logType) + DefaultDiscordLogger.logger.verbose("Created guild: \(guild)", type: self.logType) guilds[guild.id] = guild @@ -557,7 +557,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco guard fillLargeGuilds && guild.large else { return } // Fill this guild with users immediately - DefaultDiscordLogger.Logger.debug("Fill large guild \(guild.id) with all users", type: logType) + DefaultDiscordLogger.logger.debug("Fill large guild \(guild.id) with all users", type: logType) requestAllUsers(on: guild.id) } @@ -572,7 +572,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildDelete(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild delete", type: logType) + DefaultDiscordLogger.logger.log("Handling guild delete", type: logType) guard let guildId = Snowflake(data["id"] as? String) else { return } guard let removedGuild = guilds.removeValue(forKey: guildId) else { return } @@ -581,7 +581,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco channelCache[channel] = nil } - DefaultDiscordLogger.Logger.verbose("Removed guild: \(removedGuild)", type: logType) + DefaultDiscordLogger.logger.verbose("Removed guild: \(removedGuild)", type: logType) delegate?.client(self, didDeleteGuild: removedGuild) } @@ -596,14 +596,14 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildEmojiUpdate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild emoji update", type: logType) + DefaultDiscordLogger.logger.log("Handling guild emoji update", type: logType) guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let emojis = data["emojis"] as? [[String: Any]] else { return } let discordEmojis = DiscordEmoji.emojisFromArray(emojis) - DefaultDiscordLogger.Logger.verbose("Created guild emojis: \(discordEmojis)", type: logType) + DefaultDiscordLogger.logger.verbose("Created guild emojis: \(discordEmojis)", type: logType) guild.emojis = discordEmojis @@ -620,13 +620,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildMemberAdd(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild member add", type: logType) + DefaultDiscordLogger.logger.log("Handling guild member add", type: logType) guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } let guildMember = DiscordGuildMember(guildMemberObject: data, guildId: guild.id, guild: guild) - DefaultDiscordLogger.Logger.verbose("Created guild member: \(guildMember)", type: logType) + DefaultDiscordLogger.logger.verbose("Created guild member: \(guildMember)", type: logType) guild.members[guildMember.user.id] = guildMember guild.memberCount += 1 @@ -644,7 +644,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildMemberRemove(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild member remove", type: logType) + DefaultDiscordLogger.logger.log("Handling guild member remove", type: logType) guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let user = data["user"] as? [String: Any], let id = Snowflake(user["id"] as? String) else { return } @@ -653,7 +653,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco guard let removedGuildMember = guild.members.removeValue(forKey: id) else { return } - DefaultDiscordLogger.Logger.verbose("Removed guild member: \(removedGuildMember)", type: logType) + DefaultDiscordLogger.logger.verbose("Removed guild member: \(removedGuildMember)", type: logType) delegate?.client(self, didRemoveGuildMember: removedGuildMember) } @@ -668,13 +668,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildMemberUpdate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild member update", type: logType) + DefaultDiscordLogger.logger.log("Handling guild member update", type: logType) guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let user = data["user"] as? [String: Any], let id = Snowflake(user["id"] as? String) else { return } guard let guildMember = guild.members[id]?.updateMember(data) else { return } - DefaultDiscordLogger.Logger.verbose("Updated guild member: \(guildMember)", type: logType) + DefaultDiscordLogger.logger.verbose("Updated guild member: \(guildMember)", type: logType) delegate?.client(self, didUpdateGuildMember: guildMember) } @@ -689,7 +689,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildMembersChunk(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild members chunk", type: logType) + DefaultDiscordLogger.logger.log("Handling guild members chunk", type: logType) guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let members = data["members"] as? [[String: Any]] else { return } @@ -711,13 +711,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildRoleCreate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild role create", type: logType) + DefaultDiscordLogger.logger.log("Handling guild role create", type: logType) guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let roleObject = data["role"] as? [String: Any] else { return } let role = DiscordRole(roleObject: roleObject) - DefaultDiscordLogger.Logger.verbose("Created role: \(role)", type: logType) + DefaultDiscordLogger.logger.verbose("Created role: \(role)", type: logType) guild.roles[role.id] = role @@ -734,13 +734,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildRoleRemove(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild role remove", type: logType) + DefaultDiscordLogger.logger.log("Handling guild role remove", type: logType) guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let roleId = Snowflake(data["role_id"] as? String) else { return } guard let removedRole = guild.roles.removeValue(forKey: roleId) else { return } - DefaultDiscordLogger.Logger.verbose("Removed role: \(removedRole)", type: logType) + DefaultDiscordLogger.logger.verbose("Removed role: \(removedRole)", type: logType) delegate?.client(self, didDeleteRole: removedRole, fromGuild: guild) } @@ -755,14 +755,14 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildRoleUpdate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild role update", type: logType) + DefaultDiscordLogger.logger.log("Handling guild role update", type: logType) // Functionally the same as adding guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let roleObject = data["role"] as? [String: Any] else { return } let role = DiscordRole(roleObject: roleObject) - DefaultDiscordLogger.Logger.verbose("Updated role: \(role)", type: logType) + DefaultDiscordLogger.logger.verbose("Updated role: \(role)", type: logType) guild.roles[role.id] = role @@ -779,12 +779,12 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildUpdate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling guild update", type: logType) + DefaultDiscordLogger.logger.log("Handling guild update", type: logType) guard let guildId = Snowflake(data["id"] as? String) else { return } guard let updatedGuild = guilds[guildId]?.updateGuild(fromGuildUpdate: data) else { return } - DefaultDiscordLogger.Logger.verbose("Updated guild: \(updatedGuild)", type: logType) + DefaultDiscordLogger.logger.verbose("Updated guild: \(updatedGuild)", type: logType) delegate?.client(self, didUpdateGuild: updatedGuild) } @@ -799,11 +799,11 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleMessageUpdate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling message update", type: logType) + DefaultDiscordLogger.logger.log("Handling message update", type: logType) let message = DiscordMessage(messageObject: data, client: self) - DefaultDiscordLogger.Logger.verbose("Message: \(message)", type: logType) + DefaultDiscordLogger.logger.verbose("Message: \(message)", type: logType) delegate?.client(self, didUpdateMessage: message) } @@ -818,11 +818,11 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleMessageCreate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling message create", type: logType) + DefaultDiscordLogger.logger.log("Handling message create", type: logType) let message = DiscordMessage(messageObject: data, client: self) - DefaultDiscordLogger.Logger.verbose("Message: \(message)", type: logType) + DefaultDiscordLogger.logger.verbose("Message: \(message)", type: logType) delegate?.client(self, didCreateMessage: message) } @@ -849,7 +849,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco } if !discardPresences { - DefaultDiscordLogger.Logger.debug("Updated presence: \(presence!)", type: logType) + DefaultDiscordLogger.logger.debug("Updated presence: \(presence!)", type: logType) guild.presences[userId] = presence! } @@ -869,7 +869,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleReady(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling ready", type: logType) + DefaultDiscordLogger.logger.log("Handling ready", type: logType) if let user = data["user"] as? [String: Any] { self.user = DiscordUser(userObject: user) @@ -902,8 +902,8 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleVoiceServerUpdate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling voice server update", type: logType) - DefaultDiscordLogger.Logger.verbose("Voice server update: \(data)", type: logType) + DefaultDiscordLogger.logger.log("Handling voice server update", type: logType) + DefaultDiscordLogger.logger.verbose("Voice server update: \(data)", type: logType) let info = DiscordVoiceServerInformation(voiceServerInformationObject: data) @@ -922,13 +922,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleVoiceStateUpdate(with data: [String: Any]) { - DefaultDiscordLogger.Logger.log("Handling voice state update", type: logType) + DefaultDiscordLogger.logger.log("Handling voice state update", type: logType) guard let guildId = Snowflake(data["guild_id"] as? String) else { return } let state = DiscordVoiceState(voiceStateObject: data, guildId: guildId) - DefaultDiscordLogger.Logger.verbose("Voice state: \(state)", type: logType) + DefaultDiscordLogger.logger.verbose("Voice state: \(state)", type: logType) if state.channelId == 0 { guilds[guildId]?.voiceStates[state.userId] = nil diff --git a/Sources/SwiftDiscord/DiscordJSON.swift b/Sources/SwiftDiscord/DiscordJSON.swift index 43b6d14a2..4993990e4 100644 --- a/Sources/SwiftDiscord/DiscordJSON.swift +++ b/Sources/SwiftDiscord/DiscordJSON.swift @@ -30,10 +30,10 @@ enum JSON { do { return try encoder.encode(object) } catch let error as EncodingError { - DefaultDiscordLogger.Logger.error("Failed to encode json \(object): \(error.localizedDescription)", type: "JSON") + DefaultDiscordLogger.logger.error("Failed to encode json \(object): \(error.localizedDescription)", type: "JSON") return nil } catch { - DefaultDiscordLogger.Logger.error("Failed to encode json \(object): \(error)", type: "JSON") + DefaultDiscordLogger.logger.error("Failed to encode json \(object): \(error)", type: "JSON") return nil } } @@ -58,26 +58,26 @@ enum JSON { static func jsonFromResponse(data: Data?, response: HTTPURLResponse?) -> JSON? { guard let response = response else { - DefaultDiscordLogger.Logger.error("No response from jsonFromResponse", type: "JSON") + DefaultDiscordLogger.logger.error("No response from jsonFromResponse", type: "JSON") return nil } guard let data = data, let stringData = String(data: data, encoding: .utf8) else { - DefaultDiscordLogger.Logger.error("Not string data? Response code: \(response.statusCode)", type: "JSON") + DefaultDiscordLogger.logger.error("Not string data? Response code: \(response.statusCode)", type: "JSON") return nil } guard response.statusCode != 204 else { - DefaultDiscordLogger.Logger.debug("Response code 204: No content", type: "JSON") + DefaultDiscordLogger.logger.debug("Response code 204: No content", type: "JSON") return nil } guard response.statusCode == 200 || response.statusCode == 201 else { - DefaultDiscordLogger.Logger.error("Invalid response code \(response.statusCode)", type: "JSON") - DefaultDiscordLogger.Logger.error("Response: \(stringData)", type: "JSON") + DefaultDiscordLogger.logger.error("Invalid response code \(response.statusCode)", type: "JSON") + DefaultDiscordLogger.logger.error("Response: \(stringData)", type: "JSON") return nil } diff --git a/Sources/SwiftDiscord/DiscordLogger.swift b/Sources/SwiftDiscord/DiscordLogger.swift index 49fa58aa9..8c04b1a2b 100644 --- a/Sources/SwiftDiscord/DiscordLogger.swift +++ b/Sources/SwiftDiscord/DiscordLogger.swift @@ -16,6 +16,7 @@ // DEALINGS IN THE SOFTWARE. import Foundation +import Logging /// Represents the level of verbosity for the logger. public enum DiscordLogLevel { @@ -40,6 +41,9 @@ public protocol DiscordLogger { // MARK: Methods + /// Error Messages. + func error(_ message: @autoclosure () -> String, type: String) + /// Normal log messages. func log( _ message: @autoclosure () -> String, type: String) @@ -49,51 +53,42 @@ public protocol DiscordLogger { /// Debug messages. func debug(_ message: @autoclosure () -> String, type: String) - /// Error Messages. - func error(_ message: @autoclosure () -> String, type: String) + /// Trace Messages. + func trace(_ message: @autoclosure () -> String, type: String) } -public extension DiscordLogger { +class DefaultDiscordLogger : DiscordLogger { + static var logger: DiscordLogger = DefaultDiscordLogger() + + private var downstreamLogger = Logger(label: "SwiftDiscord") + var level = DiscordLogLevel.none + + /// Error Messages. + func error(_ message: @autoclosure () -> String, type: String) { + abstractLog(.error, message: message(), type: type) + } + /// Normal log messages. func log(_ message: @autoclosure () -> String, type: String) { - guard level == .info || level == .verbose || level == .debug || level == .trace else { return } - - abstractLog("LOG", message: message(), type: type) + abstractLog(.info, message: message(), type: type) } /// More info on log messages. func verbose(_ message: @autoclosure () -> String, type: String) { - guard level == .verbose || level == .debug || level == .trace else { return } - - abstractLog("VERBOSE", message: message(), type: type) + abstractLog(.debug, message: message(), type: type) } /// Debug messages. func debug(_ message: @autoclosure () -> String, type: String) { - guard level == .debug || level == .trace else { return } - - abstractLog("DEBUG", message: message(), type: type) - } - - /// Error Messages. - func error(_ message: @autoclosure () -> String, type: String) { - abstractLog("ERROR", message: message(), type: type) + abstractLog(.debug, message: message(), type: type) } - /// trace Messages. + /// Trace Messages. func trace(_ message: @autoclosure () -> String, type: String) { - guard level == .trace else { return } - - abstractLog("TRACE", message: message(), type: type) + abstractLog(.trace, message: message(), type: type) } - private func abstractLog(_ logType: String, message: String, type: String) { - NSLog("\(logType): \(type): \(message)") + private func abstractLog(_ level: Logger.Level, message: String, type: String) { + downstreamLogger.log(level: level, "\(type): \(message)") } } - -class DefaultDiscordLogger : DiscordLogger { - static var Logger: DiscordLogger = DefaultDiscordLogger() - - var level = DiscordLogLevel.none -} diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift index 1b72d2a12..ad82dba89 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift @@ -146,7 +146,7 @@ open class DiscordEngine : DiscordEngineSpec { /// Disconnects the engine. An `engine.disconnect` is fired on disconnection. /// public func disconnect() { - DefaultDiscordLogger.Logger.log("Disconnecting, \(description)", type: "DiscordWebSocketable") + DefaultDiscordLogger.logger.log("Disconnecting, \(description)", type: "DiscordWebSocketable") closed = true @@ -163,7 +163,7 @@ open class DiscordEngine : DiscordEngineSpec { connected = false - DefaultDiscordLogger.Logger.log("Disconnected, shard: \(shardNum)", type: logType) + DefaultDiscordLogger.logger.log("Disconnected, shard: \(shardNum)", type: logType) if closeReason == .sessionTimeout { sessionId = nil @@ -185,7 +185,7 @@ open class DiscordEngine : DiscordEngineSpec { /// open func handleDispatch(_ payload: DiscordGatewayPayload) { guard let type = payload.name, let event = DiscordDispatchEvent(rawValue: type) else { - DefaultDiscordLogger.Logger.error("Could not create dispatch event \(payload)", type: logType) + DefaultDiscordLogger.logger.error("Could not create dispatch event \(payload)", type: logType) return } @@ -217,9 +217,9 @@ open class DiscordEngine : DiscordEngineSpec { func _handleGatewayPayload(_ payload: DiscordGatewayPayload) { func handleInvalidSession() { if case let .bool(netsplit) = payload.payload, netsplit { - DefaultDiscordLogger.Logger.log("Netsplit recieved, trying to resume", type: logType) + DefaultDiscordLogger.logger.log("Netsplit recieved, trying to resume", type: logType) } else { - DefaultDiscordLogger.Logger.log("Invalid session received. Invalidating session", type: logType) + DefaultDiscordLogger.logger.log("Invalid session received. Invalidating session", type: logType) sessionId = nil } @@ -247,7 +247,7 @@ open class DiscordEngine : DiscordEngineSpec { sendPayload(DiscordGatewayPayload(code: .gateway(.heartbeat), payload: .integer(lastSequenceNumber))) case .heartbeatAck: heartbeatQueue.sync { self.pongsMissed = 0 } - DefaultDiscordLogger.Logger.debug("Got heartbeat ack", type: logType) + DefaultDiscordLogger.logger.debug("Got heartbeat ack", type: logType) default: error(message: "Unhandled payload: \(payload.code)") } @@ -275,7 +275,7 @@ open class DiscordEngine : DiscordEngineSpec { /// Handles the resumed event. You shouldn't call this directly. /// open func handleResumed(_ payload: DiscordGatewayPayload) { - DefaultDiscordLogger.Logger.log("Resumed gateway session on shard: \(shardNum)", type: logType) + DefaultDiscordLogger.logger.log("Resumed gateway session on shard: \(shardNum)", type: logType) heartbeatQueue.sync { self.pongsMissed = 0 } resuming = false @@ -306,12 +306,12 @@ open class DiscordEngine : DiscordEngineSpec { /// open func resumeGateway() { guard !resuming && !closed else { - DefaultDiscordLogger.Logger.log("Already trying to resume or closed, ignoring", type: logType) + DefaultDiscordLogger.logger.log("Already trying to resume or closed, ignoring", type: logType) return } - DefaultDiscordLogger.Logger.log("Trying to resume gateway session on shard: \(shardNum)", type: logType) + DefaultDiscordLogger.logger.log("Trying to resume gateway session on shard: \(shardNum)", type: logType) resuming = true @@ -322,7 +322,7 @@ open class DiscordEngine : DiscordEngineSpec { handleQueue.asyncAfter(deadline: DispatchTime.now() + Double(wait)) {[weak self] in guard let this = self, this.resuming else { return } - DefaultDiscordLogger.Logger.debug("Calling engine connect for gateway resume with wait: \(wait)", + DefaultDiscordLogger.logger.debug("Calling engine connect for gateway resume with wait: \(wait)", type: this.logType) this.connect() @@ -337,14 +337,14 @@ open class DiscordEngine : DiscordEngineSpec { /// open func sendHeartbeat() { guard connected else { - DefaultDiscordLogger.Logger.error("Tried heartbeating on disconnected shard, shard: \(shardNum)", + DefaultDiscordLogger.logger.error("Tried heartbeating on disconnected shard, shard: \(shardNum)", type: logType) return } guard pongsMissed < 2 else { - DefaultDiscordLogger.Logger.log("Too many pongs missed; closing, shard: \(shardNum)", type: logType) + DefaultDiscordLogger.logger.log("Too many pongs missed; closing, shard: \(shardNum)", type: logType) pongsMissed = 0 closeWebSockets(fast: true) @@ -352,7 +352,7 @@ open class DiscordEngine : DiscordEngineSpec { return } - DefaultDiscordLogger.Logger.debug("Sending heartbeat, shard: \(shardNum)", type: logType) + DefaultDiscordLogger.logger.debug("Sending heartbeat, shard: \(shardNum)", type: logType) pongsMissed += 1 sendPayload(DiscordGatewayPayload(code: .gateway(.heartbeat), payload: .integer(lastSequenceNumber))) @@ -379,11 +379,11 @@ open class DiscordEngine : DiscordEngineSpec { } if sessionId != nil { - DefaultDiscordLogger.Logger.log("Sending resume, shard: \(shardNum)", type: logType) + DefaultDiscordLogger.logger.log("Sending resume, shard: \(shardNum)", type: logType) sendPayload(DiscordGatewayPayload(code: .gateway(.resume), payload: .object(resumeObject))) } else { - DefaultDiscordLogger.Logger.log("Sending handshake, shard: \(shardNum)", type: logType) + DefaultDiscordLogger.logger.log("Sending handshake, shard: \(shardNum)", type: logType) sendPayload(DiscordGatewayPayload(code: .gateway(.identify), payload: .object(handshakeObject))) } @@ -395,7 +395,7 @@ open class DiscordEngine : DiscordEngineSpec { /// - parameter milliseconds: The heartbeat interval /// public func startHeartbeat(milliseconds: Int) { - DefaultDiscordLogger.Logger.debug("Starting heartbeat, shard: \(shardNum), \(milliseconds)ms", type: logType) + DefaultDiscordLogger.logger.debug("Starting heartbeat, shard: \(shardNum), \(milliseconds)ms", type: logType) heartbeatInterval = milliseconds diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index 487de4406..ed0317b42 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -83,7 +83,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu websocket?.onText { [weak self] ws, text in guard let this = self else { return } - DefaultDiscordLogger.Logger.debug("\(this.description), Got text: \(text)", type: "DiscordWebSocketable") + DefaultDiscordLogger.logger.debug("\(this.description), Got text: \(text)", type: "DiscordWebSocketable") this.parseGatewayMessage(text) } @@ -91,7 +91,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu websocket?.onClose.whenSuccess { [weak self] in guard let this = self else { return } - DefaultDiscordLogger.Logger.log("Websocket closed, \(this.description)", type: "DiscordWebSocketable") + DefaultDiscordLogger.logger.log("Websocket closed, \(this.description)", type: "DiscordWebSocketable") this.handleClose(reason: nil) } @@ -99,7 +99,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu websocket?.onClose.whenFailure { [weak self] err in guard let this = self else { return } - DefaultDiscordLogger.Logger.log("WebSocket errored: \(err), \(this.description);", type: "DiscordWebSocketable") + DefaultDiscordLogger.logger.log("WebSocket errored: \(err), \(this.description);", type: "DiscordWebSocketable") this.handleClose(reason: nil) } @@ -113,8 +113,8 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu } private func _connect() { - DefaultDiscordLogger.Logger.log("Connecting to \(connectURL), \(description)", type: "DiscordWebSocketable") - DefaultDiscordLogger.Logger.log("Attaching WebSocket, shard: \(description)", type: "DiscordWebSocketable") + DefaultDiscordLogger.logger.log("Connecting to \(connectURL), \(description)", type: "DiscordWebSocketable") + DefaultDiscordLogger.logger.log("Attaching WebSocket, shard: \(description)", type: "DiscordWebSocketable") let url = URL(string: connectURL)! let path = url.path.isEmpty ? "/" : url.path @@ -132,7 +132,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu ) { [weak self] ws in guard let this = self else { return } - DefaultDiscordLogger.Logger.log("Websocket connected, shard: \(this.description)", type: "DiscordWebSocketable") + DefaultDiscordLogger.logger.log("Websocket connected, shard: \(this.description)", type: "DiscordWebSocketable") this.websocket = ws this.connectUUID = UUID() @@ -144,14 +144,14 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu future.whenFailure { [weak self] err in guard let this = self else { return } - DefaultDiscordLogger.Logger.log("Websocket errored, closing: \(err), \(this.description)", type: "DiscordWebSocketable") + DefaultDiscordLogger.logger.log("Websocket errored, closing: \(err), \(this.description)", type: "DiscordWebSocketable") this.handleClose(reason: err) } } internal func closeWebSockets(fast: Bool = false) { - DefaultDiscordLogger.Logger.log("Closing WebSocket, shard: \(description)", type: "DiscordWebSocketable") + DefaultDiscordLogger.logger.log("Closing WebSocket, shard: \(description)", type: "DiscordWebSocketable") guard !fast else { handleClose(reason: nil) @@ -168,6 +168,6 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu /// - parameter message: The error message /// func error(message: String) { - DefaultDiscordLogger.Logger.error(message, type: description) + DefaultDiscordLogger.logger.error(message, type: description) } } diff --git a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift index 611894063..edf19c7a3 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift @@ -97,7 +97,7 @@ public extension DiscordGatewayable where Self: DiscordWebSocketable & DiscordRu return } - DefaultDiscordLogger.Logger.debug("Sending ws: \(payloadString)", type: description) + DefaultDiscordLogger.logger.debug("Sending ws: \(payloadString)", type: description) runloop.execute { self.websocket?.send(payloadString) diff --git a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift index 0467a52ce..263e143c5 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift @@ -258,7 +258,7 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { open func manuallyShatter(withInfo info: DiscordShardInformation) { guard let delegate = self.delegate else { return } - DefaultDiscordLogger.Logger.verbose("Handling shard range \(info.shardRange)", type: "DiscordShardManager") + DefaultDiscordLogger.logger.verbose("Handling shard range \(info.shardRange)", type: "DiscordShardManager") cleanUp() @@ -310,7 +310,7 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { /// - parameter shardNum: The number of the shard that disconnected. /// open func shardDidConnect(_ shard: DiscordShard) { - DefaultDiscordLogger.Logger.verbose("Shard #\(shard.shardNum), connected", type: "DiscordShardManager") + DefaultDiscordLogger.logger.verbose("Shard #\(shard.shardNum), connected", type: "DiscordShardManager") protected { connectedShards += 1 } @@ -325,7 +325,7 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { /// - parameter shardNum: The number of the shard that disconnected. /// open func shardDidDisconnect(_ shard: DiscordShard) { - DefaultDiscordLogger.Logger.verbose("Shard #\(shard.shardNum), disconnected", type: "DiscordShardManager") + DefaultDiscordLogger.logger.verbose("Shard #\(shard.shardNum), disconnected", type: "DiscordShardManager") protected { closedShards += 1 } diff --git a/Sources/SwiftDiscord/Guild/DiscordEmoji.swift b/Sources/SwiftDiscord/Guild/DiscordEmoji.swift index 2294e11ad..cbe5123c0 100644 --- a/Sources/SwiftDiscord/Guild/DiscordEmoji.swift +++ b/Sources/SwiftDiscord/Guild/DiscordEmoji.swift @@ -50,7 +50,7 @@ public struct DiscordEmoji { if let emojiID = emoji.id { emojis[emojiID] = emoji } else { - DefaultDiscordLogger.Logger.debug("EmojisFromArray used on array with non-custom emoji", type: "DiscordEmoji") + DefaultDiscordLogger.logger.debug("EmojisFromArray used on array with non-custom emoji", type: "DiscordEmoji") } } diff --git a/Sources/SwiftDiscord/Guild/DiscordGuild.swift b/Sources/SwiftDiscord/Guild/DiscordGuild.swift index 9f64dc27f..7f74aea2b 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuild.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuild.swift @@ -162,7 +162,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { public func createChannel(with options: [DiscordEndpoint.Options.GuildCreateChannel], reason: String? = nil) { guard let client = self.client else { return } - DefaultDiscordLogger.Logger.log("Creating guild channel on \(id)", type: "DiscordGuild") + DefaultDiscordLogger.logger.log("Creating guild channel on \(id)", type: "DiscordGuild") client.createGuildChannel(on: id, options: options, reason: reason) } @@ -204,7 +204,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { guard let client = self.client else { return callback(nil, nil) } client.getGuildMember(by: userId, on: id) {member, response in - DefaultDiscordLogger.Logger.debug("Got member: \(userId)", type: "DiscordGuild") + DefaultDiscordLogger.logger.debug("Got member: \(userId)", type: "DiscordGuild") var member = member member?.guild = self @@ -276,12 +276,12 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { let userId = presence.user.id if pruneUsers && presence.status == .offline { - DefaultDiscordLogger.Logger.debug("Pruning guild member \(userId) on \(id)", type: DiscordGuild.logType) + DefaultDiscordLogger.logger.debug("Pruning guild member \(userId) on \(id)", type: DiscordGuild.logType) members[userId] = nil presences[userId] = nil } else if fillUsers && !members.contains(userId) { - DefaultDiscordLogger.Logger.debug("Should get member \(userId); pull from the API", type: DiscordGuild.logType) + DefaultDiscordLogger.logger.debug("Should get member \(userId); pull from the API", type: DiscordGuild.logType) members[lazy: userId] = .lazy({[weak self] in guard let this = self else { @@ -355,7 +355,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { public func unban(_ user: DiscordUser) { guard let client = self.client else { return } - DefaultDiscordLogger.Logger.log("Unbanning user \(user) on \(id)", type: "DiscordGuild") + DefaultDiscordLogger.logger.log("Unbanning user \(user) on \(id)", type: "DiscordGuild") client.removeGuildBan(for: user.id, on: id) } diff --git a/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift b/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift index f0aec02ac..16aa3053e 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift @@ -153,7 +153,7 @@ func guildChannel(fromObject channelObject: [String: Any], case .category: return DiscordGuildChannelCategory(categoryObject: channelObject, guildID: guildID, client: client) default: - DefaultDiscordLogger.Logger.error("Unhandled guild channel in guildChannelFromObject", + DefaultDiscordLogger.logger.error("Unhandled guild channel in guildChannelFromObject", type: "DiscordGuildChannel") return nil } diff --git a/Sources/SwiftDiscord/Guild/DiscordGuildMember.swift b/Sources/SwiftDiscord/Guild/DiscordGuildMember.swift index f6ce94588..965140a18 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuildMember.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuildMember.swift @@ -115,7 +115,7 @@ public struct DiscordGuildMember { for guildMember in guildMembersArray { guard let user = guildMember["user"] as? [String: Any], let id = Snowflake(user["id"] as? String) else { - DefaultDiscordLogger.Logger.error("Couldn't extract userId from user JSON", type: "GuildMembersFromArray") + DefaultDiscordLogger.logger.error("Couldn't extract userId from user JSON", type: "GuildMembersFromArray") continue } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 18adb73b3..9afd8bae5 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -457,14 +457,14 @@ public extension DiscordEndpoint { private func createURL(getParams: [String: String]?) -> URL? { // This can fail, specifically if you try to include a non-url-encoded emoji in it guard let url = URL(string: self.combined) else { - DefaultDiscordLogger.Logger.error("Couldn't convert \"\(self.combined)\" to a URL. This shouldn't happen.", + DefaultDiscordLogger.logger.error("Couldn't convert \"\(self.combined)\" to a URL. This shouldn't happen.", type: "DiscordEndpoint") return nil } guard let getParams = getParams else { return url } guard var com = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - DefaultDiscordLogger.Logger.error("Couldn't convert \"\(url)\" to URLComponents. This shouldn't happen.", + DefaultDiscordLogger.logger.error("Couldn't convert \"\(url)\" to URLComponents. This shouldn't happen.", type: "DiscordEndpoint") return nil } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift index 8eab7c332..bb27a8335 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift @@ -342,8 +342,8 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { extraHeaders: nil) } - DefaultDiscordLogger.Logger.log("Sending message to: \(channelId)", type: "DiscordEndpointChannels") - DefaultDiscordLogger.Logger.verbose("Message: \(message)", type: "DiscordEndpointChannels") + DefaultDiscordLogger.logger.log("Sending message to: \(channelId)", type: "DiscordEndpointChannels") + DefaultDiscordLogger.logger.verbose("Message: \(message)", type: "DiscordEndpointChannels") let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(message)? = JSON.jsonFromResponse(data: data, response: response) else { diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift index 967220481..2f4c01405 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift @@ -111,8 +111,8 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } } - DefaultDiscordLogger.Logger.log("Creating a new role on \(guildId)", type: "DiscordEndpointGuild") - DefaultDiscordLogger.Logger.verbose("Role options \(roleData)", type: "DiscordEndpointGuild") + DefaultDiscordLogger.logger.log("Creating a new role on \(guildId)", type: "DiscordEndpointGuild") + DefaultDiscordLogger.logger.verbose("Role options \(roleData)", type: "DiscordEndpointGuild") guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(roleData)) else { return callback(nil, nil) } @@ -155,7 +155,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { func getGuildAuditLog(for guildId: GuildID, withOptions options: [DiscordEndpoint.Options.AuditLog], callback: @escaping (DiscordAuditLog?, HTTPURLResponse?) -> ()) { - DefaultDiscordLogger.Logger.debug("Getting audit log for \(guildId)", type: "DiscordEndpointGuild") + DefaultDiscordLogger.logger.debug("Getting audit log for \(guildId)", type: "DiscordEndpointGuild") var getParams = [String: String]() @@ -181,7 +181,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { return } - DefaultDiscordLogger.Logger.debug("Got audit log for \(guildId)", type: "DiscordEndpointGuild") + DefaultDiscordLogger.logger.debug("Got audit log for \(guildId)", type: "DiscordEndpointGuild") callback(DiscordAuditLog(auditLogObject: log), response) } @@ -202,7 +202,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { return } - DefaultDiscordLogger.Logger.debug("Got guild bans \(bans)", type: "DiscordEndpointGuild") + DefaultDiscordLogger.logger.debug("Got guild bans \(bans)", type: "DiscordEndpointGuild") callback(DiscordBan.bansFromArray(bans as! [[String: Any]]), response) } @@ -421,7 +421,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(patchParams)) else { return } - DefaultDiscordLogger.Logger.debug("Modifying guild member \(id) with options: \(patchParams) on \(guildId)", + DefaultDiscordLogger.logger.debug("Modifying guild member \(id) with options: \(patchParams) on \(guildId)", type: "DiscordEndpointGuild") rateLimiter.executeRequest(endpoint: .guildMember(guild: guildId, user: id), @@ -463,7 +463,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { on guildId: GuildID, reason: String? = nil, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { - DefaultDiscordLogger.Logger.log("Unbanning \(userId) on \(guildId)", type: "DiscordEndpointGuild") + DefaultDiscordLogger.logger.log("Unbanning \(userId) on \(guildId)", type: "DiscordEndpointGuild") var extraHeaders = [DiscordHeader: String]() diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift index dfb02680b..6a6b019dd 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift @@ -51,7 +51,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { return } - DefaultDiscordLogger.Logger.debug("Got DMChannels: \(channels)", type: "DiscordEndpointUser") + DefaultDiscordLogger.logger.debug("Got DMChannels: \(channels)", type: "DiscordEndpointUser") callback(DiscordDMChannel.DMsfromArray(channels as! [[String: Any]]), response) } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift index e10783a8f..298a6c653 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift @@ -42,7 +42,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } } - DefaultDiscordLogger.Logger.debug("Creating webhook on: \(channelId)", type: "DiscordEndpointChannels") + DefaultDiscordLogger.logger.debug("Creating webhook on: \(channelId)", type: "DiscordEndpointChannels") guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(createJSON)) else { return } @@ -119,7 +119,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation func getWebhooks(forGuild guildId: GuildID, callback: @escaping ([DiscordWebhook], HTTPURLResponse?) -> ()) { - DefaultDiscordLogger.Logger.debug("Getting webhooks for guild: \(guildId)", type: "DiscordEndpointWebhooks") + DefaultDiscordLogger.logger.debug("Getting webhooks for guild: \(guildId)", type: "DiscordEndpointWebhooks") let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(webhooks)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -160,7 +160,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(createJSON)) else { return } - DefaultDiscordLogger.Logger.debug("Modifying webhook: \(webhookId)", type: "DiscordEndpointChannels") + DefaultDiscordLogger.logger.debug("Modifying webhook: \(webhookId)", type: "DiscordEndpointChannels") let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(webhook)? = JSON.jsonFromResponse(data: data, response: response) else { diff --git a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift index ab315e8cc..b2d55fba1 100644 --- a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift +++ b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift @@ -73,7 +73,7 @@ public final class DiscordRateLimiter : DiscordRateLimiterSpec { let rateLimit = endpointLimits[endpointKey]! if rateLimit.atLimit { - DefaultDiscordLogger.Logger.debug("Hit rate limit: \(rateLimit)", type: "DiscordRateLimiter") + DefaultDiscordLogger.logger.debug("Hit rate limit: \(rateLimit)", type: "DiscordRateLimiter") guard !failFast else { callbackQueue.async { callback(nil, nil, nil) } @@ -89,7 +89,7 @@ public final class DiscordRateLimiter : DiscordRateLimiterSpec { rateLimit.remaining -= 1 - DefaultDiscordLogger.Logger.debug("Doing request: \(request), remaining: \(rateLimit.remaining)", type: "DiscordRateLimiter") + DefaultDiscordLogger.logger.debug("Doing request: \(request), remaining: \(rateLimit.remaining)", type: "DiscordRateLimiter") session.dataTask(with: request, completionHandler: createResponseHandler(for: request, endpointKey: endpointKey, @@ -157,9 +157,9 @@ public final class DiscordRateLimiter : DiscordRateLimiterSpec { reset: Int(reset as! String)!) } - DefaultDiscordLogger.Logger.debug("New limit: \(rateLimit.limit)", type: "DiscordRateLimiter") - DefaultDiscordLogger.Logger.debug("New remaining: \(rateLimit.remaining)", type: "DiscordRateLimiter") - DefaultDiscordLogger.Logger.debug("New reset: \(rateLimit.reset)", type: "DiscordRateLimiter") + DefaultDiscordLogger.logger.debug("New limit: \(rateLimit.limit)", type: "DiscordRateLimiter") + DefaultDiscordLogger.logger.debug("New remaining: \(rateLimit.remaining)", type: "DiscordRateLimiter") + DefaultDiscordLogger.logger.debug("New reset: \(rateLimit.reset)", type: "DiscordRateLimiter") callbackQueue.async { callback(data, response, error) } } @@ -327,7 +327,7 @@ private final class DiscordRateLimit { scheduledReset = true queue.asyncAfter(deadline: deadlineForReset) { - DefaultDiscordLogger.Logger.debug("Reset triggered: \(self.endpointKey)", type: "RateLimit") + DefaultDiscordLogger.logger.debug("Reset triggered: \(self.endpointKey)", type: "RateLimit") self.remaining = self.limit self.scheduledReset = false @@ -343,7 +343,7 @@ private final class DiscordRateLimit { removed += 1 } while removed < self.remaining && self.queue.count != 0 - DefaultDiscordLogger.Logger.debug("Sent \(removed) requests for limit: \(self.endpointKey)", type: "RateLimit") + DefaultDiscordLogger.logger.debug("Sent \(removed) requests for limit: \(self.endpointKey)", type: "RateLimit") } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift index 7ee7780c9..528e763cb 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift @@ -139,7 +139,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { } deinit { - DefaultDiscordLogger.Logger.debug("deinit", type: DiscordBufferedVoiceDataSource.logType) + DefaultDiscordLogger.logger.debug("deinit", type: DiscordBufferedVoiceDataSource.logType) guard !closed else { return } @@ -167,7 +167,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { open func createDispatchIO() { self.source = DispatchIO(type: .stream, fileDescriptor: pipe.fileHandleForReading.fileDescriptor, queue: encoderQueue, cleanupHandler: {code in - DefaultDiscordLogger.Logger.debug("Source spent: \(code)", type: DiscordBufferedVoiceDataSource.logType) + DefaultDiscordLogger.logger.debug("Source spent: \(code)", type: DiscordBufferedVoiceDataSource.logType) }) } @@ -185,12 +185,12 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { encoderQueue.sync { done = self.done - DefaultDiscordLogger.Logger.trace("Buffer state: count: \(self.readBuffer.count) drain: \(self.drain)", + DefaultDiscordLogger.logger.trace("Buffer state: count: \(self.readBuffer.count) drain: \(self.drain)", type: DiscordBufferedVoiceDataSource.logType) if self.drain && self.readBuffer.count <= self.drainThreshold { // The swamp has been drained, start reading again - DefaultDiscordLogger.Logger.debug("Buffer drained, scheduling read", type: DiscordBufferedVoiceDataSource.logType) + DefaultDiscordLogger.logger.debug("Buffer drained, scheduling read", type: DiscordBufferedVoiceDataSource.logType) self.drain = false self.startReading() @@ -217,7 +217,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { open func finishUpAndClose() { guard !closed else { return } - DefaultDiscordLogger.Logger.debug("Closing pipe for writing", type: DiscordBufferedVoiceDataSource.logType) + DefaultDiscordLogger.logger.debug("Closing pipe for writing", type: DiscordBufferedVoiceDataSource.logType) writeToHandler.closeFile() @@ -243,23 +243,23 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { guard let this = self else { return } guard let data = data, data.count > 0 else { - DefaultDiscordLogger.Logger.debug("No data, reader probably closed", + DefaultDiscordLogger.logger.debug("No data, reader probably closed", type: DiscordBufferedVoiceDataSource.logType) this.done = true if done && code == 0 { // EOF reached - DefaultDiscordLogger.Logger.debug("Reader done", type: DiscordBufferedVoiceDataSource.logType) + DefaultDiscordLogger.logger.debug("Reader done", type: DiscordBufferedVoiceDataSource.logType) } else { - DefaultDiscordLogger.Logger.debug("Something is weird \(done) \(code)", + DefaultDiscordLogger.logger.debug("Something is weird \(done) \(code)", type: DiscordBufferedVoiceDataSource.logType) } return } - DefaultDiscordLogger.Logger.debug("Read \(data.count) bytes", type: DiscordBufferedVoiceDataSource.logType) + DefaultDiscordLogger.logger.debug("Read \(data.count) bytes", type: DiscordBufferedVoiceDataSource.logType) do { try data.withUnsafeBytes {(bytes: UnsafePointer) in @@ -269,7 +269,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { guard this.readBuffer.count < this.bufferSize else { // Buffer is full; wait till it's drained // Whatever is in charge of taking from the buffer should queue up more reading - DefaultDiscordLogger.Logger.debug("Buffer full, not reading again", + DefaultDiscordLogger.logger.debug("Buffer full, not reading again", type: DiscordBufferedVoiceDataSource.logType) this.drain = true @@ -278,7 +278,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { this._read() } catch { - DefaultDiscordLogger.Logger.error("Error encoding bytes", type: DiscordBufferedVoiceDataSource.logType) + DefaultDiscordLogger.logger.error("Error encoding bytes", type: DiscordBufferedVoiceDataSource.logType) } } } @@ -325,7 +325,7 @@ open class DiscordVoiceFileDataSource : DiscordBufferedVoiceDataSource { } deinit { - DefaultDiscordLogger.Logger.debug("deinit", type: DiscordVoiceFileDataSource.logType) + DefaultDiscordLogger.logger.debug("deinit", type: DiscordVoiceFileDataSource.logType) } // MARK: Methods @@ -338,7 +338,7 @@ open class DiscordVoiceFileDataSource : DiscordBufferedVoiceDataSource { open override func createDispatchIO() { self.source = DispatchIO(type: .stream, fileDescriptor: wrappedFile.fileDescriptor, queue: encoderQueue, cleanupHandler: {code in - DefaultDiscordLogger.Logger.debug("Source spent: \(code)", type: DiscordVoiceFileDataSource.logType) + DefaultDiscordLogger.logger.debug("Source spent: \(code)", type: DiscordVoiceFileDataSource.logType) }) } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift index fbaee8e82..f73fa27e1 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift @@ -37,10 +37,10 @@ open class DiscordVoiceSessionDecoder { let decoder: DiscordOpusDecoder if let previous = decoders[packet.ssrc] { - DefaultDiscordLogger.Logger.debug("Reusing decoder for ssrc: \(packet.ssrc), seqNum: \(packet.seqNum), timestamp: \(packet.timestamp)", type: "DiscordVoiceSessionDecoder") + DefaultDiscordLogger.logger.debug("Reusing decoder for ssrc: \(packet.ssrc), seqNum: \(packet.seqNum), timestamp: \(packet.timestamp)", type: "DiscordVoiceSessionDecoder") decoder = previous } else { - DefaultDiscordLogger.Logger.debug("New decoder for ssrc: \(packet.ssrc), seqNum: \(packet.seqNum), timestamp: \(packet.timestamp)", type: "DiscordVoiceSessionDecoder") + DefaultDiscordLogger.logger.debug("New decoder for ssrc: \(packet.ssrc), seqNum: \(packet.seqNum), timestamp: \(packet.timestamp)", type: "DiscordVoiceSessionDecoder") decoder = try DiscordOpusDecoder(sampleRate: 48_000, channels: 2) decoders[packet.ssrc] = decoder } @@ -61,8 +61,8 @@ open class DiscordVoiceSessionDecoder { sequences[packet.ssrc] = packet.seqNum timestamps[packet.ssrc] = packet.timestamp - DefaultDiscordLogger.Logger.debug("Out of order packet", type: "DiscordVoiceSessionDecoder") - DefaultDiscordLogger.Logger.debug("Looks to have a sequence difference of \(packet.seqNum - previousSeqNum)", type: "DiscordVoiceSessionDecoder") + DefaultDiscordLogger.logger.debug("Out of order packet", type: "DiscordVoiceSessionDecoder") + DefaultDiscordLogger.logger.debug("Looks to have a sequence difference of \(packet.seqNum - previousSeqNum)", type: "DiscordVoiceSessionDecoder") for _ in 0.. (String, Int) { - DefaultDiscordLogger.Logger.debug("Extracting ip and port from \(bytes)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Extracting ip and port from \(bytes)", type: DiscordVoiceEngine.logType) let ipData = bytes.dropLast(2) let portBytes = Array(bytes.suffix(from: bytes.endIndex.advanced(by: -2))) @@ -329,17 +329,17 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { do { sendVoiceData(try source.engineNeedsData(self)) } catch DiscordVoiceDataSourceStatus.noData { - DefaultDiscordLogger.Logger.trace("No data", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.trace("No data", type: DiscordVoiceEngine.logType) if speaking { sendSpeaking(false) } } catch DiscordVoiceDataSourceStatus.done { - DefaultDiscordLogger.Logger.debug("Voice source done, sending silence", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Voice source done, sending silence", type: DiscordVoiceEngine.logType) sendSilence(previousSource: nil) } catch let DiscordVoiceDataSourceStatus.silenceDone(source) { - DefaultDiscordLogger.Logger.debug("Voice silence done, requesting new source", + DefaultDiscordLogger.logger.debug("Voice silence done, requesting new source", type: DiscordVoiceEngine.logType) if speaking { @@ -354,7 +354,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { self.source = source } } catch { - DefaultDiscordLogger.Logger.error("Error getting voice data: \(error)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.error("Error getting voice data: \(error)", type: DiscordVoiceEngine.logType) } } @@ -364,7 +364,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// - parameter reason: The reason the socket closed. /// public func handleClose(reason: Error? = nil) { - DefaultDiscordLogger.Logger.log("Voice engine closed", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.log("Voice engine closed", type: DiscordVoiceEngine.logType) closeOutEngine() } @@ -378,11 +378,11 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// - parameter payload: The dispatch payload /// public func handleHello(_ payload: DiscordGatewayPayload) { - DefaultDiscordLogger.Logger.debug("Handling hello \(payload)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Handling hello \(payload)", type: DiscordVoiceEngine.logType) guard case let .object(helloPayload) = payload.payload, let heartbeat = helloPayload["heartbeat_interval"] as? Int else { - DefaultDiscordLogger.Logger.error("Error extracting heartbeat info \(payload)", + DefaultDiscordLogger.logger.error("Error extracting heartbeat info \(payload)", type: DiscordVoiceEngine.logType) return @@ -412,18 +412,18 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { self.sendSilence(previousSource: self.source) } case .speaking: - DefaultDiscordLogger.Logger.debug("Got speaking \(payload)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Got speaking \(payload)", type: DiscordVoiceEngine.logType) case .hello: handleHello(payload) case .heartbeatAck: - DefaultDiscordLogger.Logger.debug("Got heartbeat ack", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Got heartbeat ack", type: DiscordVoiceEngine.logType) case .resumed: handleResumed(payload) case .clientDisconnect: // Should we tell someone about this? - DefaultDiscordLogger.Logger.debug("Someone left voice channel \(payload)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Someone left voice channel \(payload)", type: DiscordVoiceEngine.logType) default: - DefaultDiscordLogger.Logger.debug("Unhandled voice payload \(payload)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Unhandled voice payload \(payload)", type: DiscordVoiceEngine.logType) } } @@ -453,7 +453,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// public func handleResumed(_ payload: DiscordGatewayPayload) { // TODO implement voice resume - DefaultDiscordLogger.Logger.debug("Should handle resumed \(payload)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Should handle resumed \(payload)", type: DiscordVoiceEngine.logType) } private func handleVoiceSessionDescription(with payload: DiscordGatewayPayloadData) { @@ -473,7 +473,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// public func parseGatewayMessage(_ string: String) { guard let decoded = DiscordGatewayPayload.payloadFromString(string, fromGateway: false) else { - DefaultDiscordLogger.Logger.log("Got unknown payload \(string)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.log("Got unknown payload \(string)", type: DiscordVoiceEngine.logType) return } @@ -495,7 +495,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { _ = try socket.readDatagram(into: &data) - DefaultDiscordLogger.Logger.debug("Received data \(data)", type: "DiscordVoiceEngine") + DefaultDiscordLogger.logger.debug("Received data \(data)", type: "DiscordVoiceEngine") guard let this = self else { return } @@ -508,9 +508,9 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { this.voiceDelegate?.voiceEngine(this, didReceiveOpusVoiceData: packet) } } catch DiscordVoiceError.initialPacket { - DefaultDiscordLogger.Logger.debug("Got initial packet", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Got initial packet", type: DiscordVoiceEngine.logType) } catch DiscordVoiceError.decodeFail { - DefaultDiscordLogger.Logger.debug("Failed to decode a packet", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Failed to decode a packet", type: DiscordVoiceEngine.logType) } catch EngineError.decryptionError { self?.error(message: "Error decrypting voice packet") } catch let err { @@ -536,7 +536,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { // currently only xsalsa20_poly1305 is supported // After this point we are good to go in sending encrypted voice packets private func selectProtocol(with ip: String, on port: Int) { - DefaultDiscordLogger.Logger.debug("Selecting UDP protocol with ip: \(ip) on port: \(port)", + DefaultDiscordLogger.logger.debug("Selecting UDP protocol with ip: \(ip) on port: \(port)", type: DiscordVoiceEngine.logType) let payloadData: [String: Any] = [ @@ -555,7 +555,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { // which will cause an ask for a new source readSource() - DefaultDiscordLogger.Logger.debug("VoiceEngine is ready!", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("VoiceEngine is ready!", type: DiscordVoiceEngine.logType) guard config.captureVoice else { return } @@ -611,7 +611,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { sendSpeaking(true) } - DefaultDiscordLogger.Logger.trace("Should send voice data: \(data.count) bytes", + DefaultDiscordLogger.logger.trace("Should send voice data: \(data.count) bytes", type: DiscordVoiceEngine.logType) do { @@ -652,7 +652,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// - parameter terminationHandler: Called when the middleware is done. Does not mean that all encoding is done. /// public func setupMiddleware(_ middleware: Process, terminationHandler: (() -> ())?) { - DefaultDiscordLogger.Logger.debug("Setting up middleware", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Setting up middleware", type: DiscordVoiceEngine.logType) // TODO this is bad, fix the types here guard let source = self.source as? DiscordBufferedVoiceDataSource else { return } @@ -663,7 +663,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { do { try source.middleware?.start() } catch { - DefaultDiscordLogger.Logger.error("Could not start middleware: \(error)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.error("Could not start middleware: \(error)", type: DiscordVoiceEngine.logType) } } #endif @@ -674,7 +674,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { public func startHandshake() { guard voiceDelegate != nil else { return } - DefaultDiscordLogger.Logger.log("Starting voice handshake", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.log("Starting voice handshake", type: DiscordVoiceEngine.logType) sendPayload(DiscordGatewayPayload(code: .voice(.identify), payload: .object(handshakeObject))) } @@ -693,7 +693,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { private func startUDP() { guard udpPort != -1, !udpIp.isEmpty else { return } - DefaultDiscordLogger.Logger.debug("Starting voice UDP connection", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.debug("Starting voice UDP connection", type: DiscordVoiceEngine.logType) do { guard let sig = try Socket.Signature( @@ -712,7 +712,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { findIP() } catch let err { // TODO Handle voice error disconnect from voice - DefaultDiscordLogger.Logger.error("UDP setup error \(err)", type: DiscordVoiceEngine.logType) + DefaultDiscordLogger.logger.error("UDP setup error \(err)", type: DiscordVoiceEngine.logType) } } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift index 6318f183c..075532dea 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift @@ -116,7 +116,7 @@ open class DiscordVoiceManager : DiscordVoiceEngineDelegate, Lockable { /// open func leaveVoiceChannel(onGuild guildId: GuildID) { guard let engine = get(voiceEngines[guildId]) else { - DefaultDiscordLogger.Logger.error("Could not find a voice engine for guild \(guildId)", type: logType) + DefaultDiscordLogger.logger.error("Could not find a voice engine for guild \(guildId)", type: logType) return } @@ -126,13 +126,13 @@ open class DiscordVoiceManager : DiscordVoiceEngineDelegate, Lockable { voiceServerInformations[guildId] = nil } - DefaultDiscordLogger.Logger.verbose("Disconnecting voice engine for guild \(guildId)", type: logType) + DefaultDiscordLogger.logger.verbose("Disconnecting voice engine for guild \(guildId)", type: logType) engine.disconnect() // Make sure everything is cleaned out - DefaultDiscordLogger.Logger.verbose("Rejoining voice channels after leave", type: logType) + DefaultDiscordLogger.logger.verbose("Rejoining voice channels after leave", type: logType) for (guildId, _) in voiceEngines { startVoiceConnection(guildId) @@ -173,7 +173,7 @@ open class DiscordVoiceManager : DiscordVoiceEngineDelegate, Lockable { secret: previousEngine?.secret ) - DefaultDiscordLogger.Logger.log("Connecting voice engine", type: logType) + DefaultDiscordLogger.logger.log("Connecting voice engine", type: logType) DispatchQueue.global().async {[weak engine = voiceEngines[guildId]!] in engine?.connect() From c591e23c19f92a1e80e2ff9311a60ecee02d31dc Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 4 Jan 2020 03:44:16 +0100 Subject: [PATCH 037/104] Migrate DiscordLogger.level to swift-log Use swift-log's Logger.Level instead of a custom enum to configure SwiftDiscord's global logger. Remove the client option which allows the user to inject a custom logger since swift-log already provides this functionality (LoggingSystem.bootstrap). --- Sources/SwiftDiscord/DiscordClient.swift | 2 -- .../SwiftDiscord/DiscordClientOption.swift | 7 ++----- Sources/SwiftDiscord/DiscordLogger.swift | 21 +++++-------------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/Sources/SwiftDiscord/DiscordClient.swift b/Sources/SwiftDiscord/DiscordClient.swift index 0868f7efb..424d9875f 100644 --- a/Sources/SwiftDiscord/DiscordClient.swift +++ b/Sources/SwiftDiscord/DiscordClient.swift @@ -118,8 +118,6 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco handleQueue = queue case let .log(level): DefaultDiscordLogger.logger.level = level - case let .logger(logger): - DefaultDiscordLogger.logger = logger case let .rateLimiter(limiter): self.rateLimiter = limiter case let .shardingInfo(shardingInfo): diff --git a/Sources/SwiftDiscord/DiscordClientOption.swift b/Sources/SwiftDiscord/DiscordClientOption.swift index 56c86d3de..65e00e1b4 100644 --- a/Sources/SwiftDiscord/DiscordClientOption.swift +++ b/Sources/SwiftDiscord/DiscordClientOption.swift @@ -16,6 +16,7 @@ // DEALINGS IN THE SOFTWARE. import Dispatch +import Logging import Foundation /// A enum representing a configuration option. @@ -35,10 +36,7 @@ public enum DiscordClientOption : CustomStringConvertible, Equatable { case handleQueue(DispatchQueue) /// The log level for the logger. - case log(DiscordLogLevel) - - /// Used to set a custom logger. - case logger(DiscordLogger) + case log(Logger.Level) /// If this option is given, the client will automatically unload users who go offline. This can save some memory. /// However this means that invsible users will also be pruned. @@ -63,7 +61,6 @@ public enum DiscordClientOption : CustomStringConvertible, Equatable { case .fillUsers: return "fillUsers" case .handleQueue: return "handleQueue" case .log: return "log" - case .logger: return "logger" case .rateLimiter: return "rateLimiter" case .shardingInfo: return "shardingInfo" case .pruneUsers: return "pruneUsers" diff --git a/Sources/SwiftDiscord/DiscordLogger.swift b/Sources/SwiftDiscord/DiscordLogger.swift index 8c04b1a2b..e2ca51a2d 100644 --- a/Sources/SwiftDiscord/DiscordLogger.swift +++ b/Sources/SwiftDiscord/DiscordLogger.swift @@ -18,26 +18,12 @@ import Foundation import Logging -/// Represents the level of verbosity for the logger. -public enum DiscordLogLevel { - /// Log nothing. - case none - /// Log connecting, disconnecting, events (but not content), etc. - case info - /// Log content of events. - case verbose - /// Log almost everything, minus the noisiest things. - case debug - /// Log everything. - case trace -} - /// Declares that a type will act as a logger. public protocol DiscordLogger { // MARK: Properties /// Whether to log or not. - var level: DiscordLogLevel { get set } + var level: Logger.Level { get set } // MARK: Methods @@ -61,7 +47,10 @@ class DefaultDiscordLogger : DiscordLogger { static var logger: DiscordLogger = DefaultDiscordLogger() private var downstreamLogger = Logger(label: "SwiftDiscord") - var level = DiscordLogLevel.none + var level: Logger.Level { + get { return downstreamLogger.logLevel } + set { downstreamLogger.logLevel = newValue } + } /// Error Messages. func error(_ message: @autoclosure () -> String, type: String) { From 67ee8a5b4e5779f9c6822f76b8e3c5cf99ed5d54 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 14 Jan 2020 23:41:45 +0100 Subject: [PATCH 038/104] Migrate entire logging API to swift-log Drop DiscordLogger and its implementations in favor of swift-log-based loggers. The 'verbose' logging level is migrated to the 'debug' level. --- .../SwiftDiscord/Channel/DiscordChannel.swift | 5 +- Sources/SwiftDiscord/DiscordClient.swift | 99 +++++++++---------- .../SwiftDiscord/DiscordClientOption.swift | 4 - Sources/SwiftDiscord/DiscordJSON.swift | 17 ++-- Sources/SwiftDiscord/DiscordLogger.swift | 83 ---------------- .../SwiftDiscord/Gateway/DiscordEngine.swift | 37 +++---- .../Gateway/DiscordEngineSpec.swift | 21 ++-- .../SwiftDiscord/Gateway/DiscordGateway.swift | 5 +- .../Gateway/DiscordSharding.swift | 9 +- Sources/SwiftDiscord/Guild/DiscordEmoji.swift | 6 +- Sources/SwiftDiscord/Guild/DiscordGuild.swift | 15 +-- .../Guild/DiscordGuildChannel.swift | 7 +- .../Guild/DiscordGuildMember.swift | 5 +- .../SwiftDiscord/Rest/DiscordEndpoint.swift | 9 +- .../DiscordEndpointConsumer+Channels.swift | 7 +- .../Rest/DiscordEndpointConsumer+Guilds.swift | 18 ++-- .../Rest/DiscordEndpointConsumer+User.swift | 5 +- .../DiscordEndpointConsumer+Webhooks.swift | 9 +- .../Rest/DiscordRateLimiter.swift | 17 ++-- .../Voice/DiscordVoiceDataSource.swift | 37 +++---- .../Voice/DiscordVoiceDecoder.swift | 11 ++- .../Voice/DiscordVoiceEngine.swift | 62 ++++++------ .../Voice/DiscordVoiceManager.swift | 11 ++- 23 files changed, 226 insertions(+), 273 deletions(-) delete mode 100644 Sources/SwiftDiscord/DiscordLogger.swift diff --git a/Sources/SwiftDiscord/Channel/DiscordChannel.swift b/Sources/SwiftDiscord/Channel/DiscordChannel.swift index b8bf92b22..6c7f1bd26 100644 --- a/Sources/SwiftDiscord/Channel/DiscordChannel.swift +++ b/Sources/SwiftDiscord/Channel/DiscordChannel.swift @@ -19,9 +19,12 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif +import Logging import class Dispatch.DispatchSemaphore +fileprivate let logger = Logger(label: "DiscordChannel") + /// Protocol that declares a type will be a Discord channel. public protocol DiscordChannel : DiscordClientHolder { // MARK: Properties @@ -72,7 +75,7 @@ public extension DiscordChannel { func delete(reason: String? = nil) { guard let client = self.client else { return } - DefaultDiscordLogger.logger.log("Deleting channel: \(id)", type: "DiscordChannel") + logger.info("Deleting channel: \(id)") client.deleteChannel(id, reason: reason) } diff --git a/Sources/SwiftDiscord/DiscordClient.swift b/Sources/SwiftDiscord/DiscordClient.swift index 424d9875f..035345dd8 100644 --- a/Sources/SwiftDiscord/DiscordClient.swift +++ b/Sources/SwiftDiscord/DiscordClient.swift @@ -17,8 +17,11 @@ import Foundation import Dispatch +import Logging import NIO +fileprivate let logger = Logger(label: "DiscordClient") + /// /// The base class for SwiftDiscord. Most interaction with Discord will be done through this class. /// @@ -95,7 +98,6 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco var channelCache = [ChannelID: DiscordChannel]() - private var logType: String { return "DiscordClient" } private let voiceQueue = DispatchQueue(label: "voiceQueue") // MARK: Initializers @@ -116,8 +118,6 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco switch config { case let .handleQueue(queue): handleQueue = queue - case let .log(level): - DefaultDiscordLogger.logger.level = level case let .rateLimiter(limiter): self.rateLimiter = limiter case let .shardingInfo(shardingInfo): @@ -149,7 +149,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// with the client. /// open func connect() { - DefaultDiscordLogger.logger.log("Connecting", type: logType) + logger.info("Connecting") shardManager.manuallyShatter(withInfo: shardingInfo) @@ -162,7 +162,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// Calling this method turns off automatic resuming, set `resume` to `true` before calling `connect()` again. /// open func disconnect() { - DefaultDiscordLogger.logger.log("Disconnecting", type: logType) + logger.info("Disconnecting") connected = false @@ -181,7 +181,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// public func findChannel(fromId channelId: ChannelID) -> DiscordChannel? { if let channel = channelCache[channelId] { - DefaultDiscordLogger.logger.debug("Got cached channel \(channel)", type: logType) + logger.debug("Got cached channel \(channel)") return channel } @@ -193,14 +193,14 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco } else if let dmChannel = directChannels[channelId] { channel = dmChannel } else { - DefaultDiscordLogger.logger.debug("Couldn't find channel \(channelId)", type: logType) + logger.debug("Couldn't find channel \(channelId)") return nil } channelCache[channel.id] = channel - DefaultDiscordLogger.logger.debug("Found channel \(channel)", type: logType) + logger.debug("Found channel \(channel)") return channel } @@ -215,8 +215,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// open func handleDispatch(event: DiscordDispatchEvent, data: DiscordGatewayPayloadData) { guard case let .object(eventData) = data else { - DefaultDiscordLogger.logger.error("Got dispatch event without an object: \(event), \(data)", - type: "DiscordDispatchEventHandler") + logger.error("Got dispatch event without an object: \(event), \(data)") return } @@ -266,7 +265,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco return } - DefaultDiscordLogger.logger.log("Joining voice channel: \(channel)", type: self.logType) + logger.info("Joining voice channel: \(channel)") shardManager.sendPayload(DiscordGatewayPayload(code: .gateway(.voiceStatusUpdate), payload: .object(["guild_id": String(describing: guild.id), @@ -283,7 +282,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter onGuild: The snowflake of the guild that you want to leave. /// open func leaveVoiceChannel(onGuild guildId: GuildID) { - DefaultDiscordLogger.logger.log("Leaving voice channel on guild: \(guildId)", type: logType) + logger.info("Leaving voice channel on guild: \(guildId)") voiceManager.leaveVoiceChannel(onGuild: guildId) } @@ -453,7 +452,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleChannelCreate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling channel create", type: logType) + logger.info("Handling channel create") guard let channel = channelFromObject(data, withClient: self) else { return } @@ -468,7 +467,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco break } - DefaultDiscordLogger.logger.verbose("Created channel: \(channel)", type: logType) + logger.debug("(verbose) Created channel: \(channel)") delegate?.client(self, didCreateChannel: channel) } @@ -483,7 +482,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleChannelDelete(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling channel delete", type: logType) + logger.info("Handling channel delete") guard let type = DiscordChannelType(rawValue: data["type"] as? Int ?? -1) else { return } guard let channelId = Snowflake(data["id"] as? String) else { return } @@ -502,7 +501,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco channelCache.removeValue(forKey: channelId) - DefaultDiscordLogger.logger.verbose("Removed channel: \(removedChannel)", type: logType) + logger.debug("(verbose) Removed channel: \(removedChannel)") delegate?.client(self, didDeleteChannel: removedChannel) } @@ -517,13 +516,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleChannelUpdate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling channel update", type: logType) + logger.info("Handling channel update") guard let channel = guildChannel(fromObject: data, guildID: nil, client: self) else { return } - DefaultDiscordLogger.logger.verbose("Updated channel: \(channel)", type: logType) + logger.debug("(verbose) Updated channel: \(channel)") guilds[channel.guildId]?.channels[channel.id] = channel @@ -542,11 +541,11 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildCreate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild create", type: logType) + logger.info("Handling guild create") let guild = DiscordGuild(guildObject: data, client: self) - DefaultDiscordLogger.logger.verbose("Created guild: \(guild)", type: self.logType) + logger.debug("(verbose) Created guild: \(guild)") guilds[guild.id] = guild @@ -555,7 +554,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco guard fillLargeGuilds && guild.large else { return } // Fill this guild with users immediately - DefaultDiscordLogger.logger.debug("Fill large guild \(guild.id) with all users", type: logType) + logger.debug("Fill large guild \(guild.id) with all users") requestAllUsers(on: guild.id) } @@ -570,7 +569,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildDelete(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild delete", type: logType) + logger.info("Handling guild delete") guard let guildId = Snowflake(data["id"] as? String) else { return } guard let removedGuild = guilds.removeValue(forKey: guildId) else { return } @@ -579,7 +578,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco channelCache[channel] = nil } - DefaultDiscordLogger.logger.verbose("Removed guild: \(removedGuild)", type: logType) + logger.debug("(verbose) Removed guild: \(removedGuild)") delegate?.client(self, didDeleteGuild: removedGuild) } @@ -594,14 +593,14 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildEmojiUpdate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild emoji update", type: logType) + logger.info("Handling guild emoji update") guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let emojis = data["emojis"] as? [[String: Any]] else { return } let discordEmojis = DiscordEmoji.emojisFromArray(emojis) - DefaultDiscordLogger.logger.verbose("Created guild emojis: \(discordEmojis)", type: logType) + logger.debug("(verbose) Created guild emojis: \(discordEmojis)") guild.emojis = discordEmojis @@ -618,13 +617,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildMemberAdd(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild member add", type: logType) + logger.info("Handling guild member add") guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } let guildMember = DiscordGuildMember(guildMemberObject: data, guildId: guild.id, guild: guild) - DefaultDiscordLogger.logger.verbose("Created guild member: \(guildMember)", type: logType) + logger.debug("(verbose) Created guild member: \(guildMember)") guild.members[guildMember.user.id] = guildMember guild.memberCount += 1 @@ -642,7 +641,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildMemberRemove(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild member remove", type: logType) + logger.info("Handling guild member remove") guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let user = data["user"] as? [String: Any], let id = Snowflake(user["id"] as? String) else { return } @@ -651,7 +650,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco guard let removedGuildMember = guild.members.removeValue(forKey: id) else { return } - DefaultDiscordLogger.logger.verbose("Removed guild member: \(removedGuildMember)", type: logType) + logger.debug("(verbose) Removed guild member: \(removedGuildMember)") delegate?.client(self, didRemoveGuildMember: removedGuildMember) } @@ -666,13 +665,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildMemberUpdate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild member update", type: logType) + logger.info("Handling guild member update") guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let user = data["user"] as? [String: Any], let id = Snowflake(user["id"] as? String) else { return } guard let guildMember = guild.members[id]?.updateMember(data) else { return } - DefaultDiscordLogger.logger.verbose("Updated guild member: \(guildMember)", type: logType) + logger.debug("(verbose) Updated guild member: \(guildMember)") delegate?.client(self, didUpdateGuildMember: guildMember) } @@ -687,7 +686,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildMembersChunk(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild members chunk", type: logType) + logger.info("Handling guild members chunk") guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let members = data["members"] as? [[String: Any]] else { return } @@ -709,13 +708,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildRoleCreate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild role create", type: logType) + logger.info("Handling guild role create") guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let roleObject = data["role"] as? [String: Any] else { return } let role = DiscordRole(roleObject: roleObject) - DefaultDiscordLogger.logger.verbose("Created role: \(role)", type: logType) + logger.debug("(verbose) Created role: \(role)") guild.roles[role.id] = role @@ -732,13 +731,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildRoleRemove(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild role remove", type: logType) + logger.info("Handling guild role remove") guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let roleId = Snowflake(data["role_id"] as? String) else { return } guard let removedRole = guild.roles.removeValue(forKey: roleId) else { return } - DefaultDiscordLogger.logger.verbose("Removed role: \(removedRole)", type: logType) + logger.debug("(verbose) Removed role: \(removedRole)") delegate?.client(self, didDeleteRole: removedRole, fromGuild: guild) } @@ -753,14 +752,14 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildRoleUpdate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild role update", type: logType) + logger.info("Handling guild role update") // Functionally the same as adding guard let guildId = Snowflake(data["guild_id"] as? String), let guild = guilds[guildId] else { return } guard let roleObject = data["role"] as? [String: Any] else { return } let role = DiscordRole(roleObject: roleObject) - DefaultDiscordLogger.logger.verbose("Updated role: \(role)", type: logType) + logger.debug("(verbose) Updated role: \(role)") guild.roles[role.id] = role @@ -777,12 +776,12 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleGuildUpdate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling guild update", type: logType) + logger.info("Handling guild update") guard let guildId = Snowflake(data["id"] as? String) else { return } guard let updatedGuild = guilds[guildId]?.updateGuild(fromGuildUpdate: data) else { return } - DefaultDiscordLogger.logger.verbose("Updated guild: \(updatedGuild)", type: logType) + logger.debug("(verbose) Updated guild: \(updatedGuild)") delegate?.client(self, didUpdateGuild: updatedGuild) } @@ -797,11 +796,11 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleMessageUpdate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling message update", type: logType) + logger.info("Handling message update") let message = DiscordMessage(messageObject: data, client: self) - DefaultDiscordLogger.logger.verbose("Message: \(message)", type: logType) + logger.debug("(verbose) Message: \(message)") delegate?.client(self, didUpdateMessage: message) } @@ -816,11 +815,11 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleMessageCreate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling message create", type: logType) + logger.info("Handling message create") let message = DiscordMessage(messageObject: data, client: self) - DefaultDiscordLogger.logger.verbose("Message: \(message)", type: logType) + logger.debug("(verbose) Message: \(message)") delegate?.client(self, didCreateMessage: message) } @@ -847,7 +846,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco } if !discardPresences { - DefaultDiscordLogger.logger.debug("Updated presence: \(presence!)", type: logType) + logger.debug("Updated presence: \(presence!)") guild.presences[userId] = presence! } @@ -867,7 +866,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleReady(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling ready", type: logType) + logger.info("Handling ready") if let user = data["user"] as? [String: Any] { self.user = DiscordUser(userObject: user) @@ -900,8 +899,8 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleVoiceServerUpdate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling voice server update", type: logType) - DefaultDiscordLogger.logger.verbose("Voice server update: \(data)", type: logType) + logger.info("Handling voice server update") + logger.debug("(verbose) Voice server update: \(data)") let info = DiscordVoiceServerInformation(voiceServerInformationObject: data) @@ -920,13 +919,13 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleVoiceStateUpdate(with data: [String: Any]) { - DefaultDiscordLogger.logger.log("Handling voice state update", type: logType) + logger.info("Handling voice state update") guard let guildId = Snowflake(data["guild_id"] as? String) else { return } let state = DiscordVoiceState(voiceStateObject: data, guildId: guildId) - DefaultDiscordLogger.logger.verbose("Voice state: \(state)", type: logType) + logger.debug("(verbose) Voice state: \(state)") if state.channelId == 0 { guilds[guildId]?.voiceStates[state.userId] = nil diff --git a/Sources/SwiftDiscord/DiscordClientOption.swift b/Sources/SwiftDiscord/DiscordClientOption.swift index 65e00e1b4..c986dc788 100644 --- a/Sources/SwiftDiscord/DiscordClientOption.swift +++ b/Sources/SwiftDiscord/DiscordClientOption.swift @@ -35,9 +35,6 @@ public enum DiscordClientOption : CustomStringConvertible, Equatable { /// This is also the queue that properties should be read from. case handleQueue(DispatchQueue) - /// The log level for the logger. - case log(Logger.Level) - /// If this option is given, the client will automatically unload users who go offline. This can save some memory. /// However this means that invsible users will also be pruned. case pruneUsers @@ -60,7 +57,6 @@ public enum DiscordClientOption : CustomStringConvertible, Equatable { case .fillLargeGuilds: return "fillLargeGuilds" case .fillUsers: return "fillUsers" case .handleQueue: return "handleQueue" - case .log: return "log" case .rateLimiter: return "rateLimiter" case .shardingInfo: return "shardingInfo" case .pruneUsers: return "pruneUsers" diff --git a/Sources/SwiftDiscord/DiscordJSON.swift b/Sources/SwiftDiscord/DiscordJSON.swift index 4993990e4..fa728be69 100644 --- a/Sources/SwiftDiscord/DiscordJSON.swift +++ b/Sources/SwiftDiscord/DiscordJSON.swift @@ -19,6 +19,9 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif +import Logging + +fileprivate let logger = Logger(label: "DiscordJSON") enum JSON { case array([Any]) @@ -30,10 +33,10 @@ enum JSON { do { return try encoder.encode(object) } catch let error as EncodingError { - DefaultDiscordLogger.logger.error("Failed to encode json \(object): \(error.localizedDescription)", type: "JSON") + logger.error("Failed to encode json \(object): \(error.localizedDescription)") return nil } catch { - DefaultDiscordLogger.logger.error("Failed to encode json \(object): \(error)", type: "JSON") + logger.error("Failed to encode json \(object): \(error)") return nil } } @@ -58,26 +61,26 @@ enum JSON { static func jsonFromResponse(data: Data?, response: HTTPURLResponse?) -> JSON? { guard let response = response else { - DefaultDiscordLogger.logger.error("No response from jsonFromResponse", type: "JSON") + logger.error("No response from jsonFromResponse") return nil } guard let data = data, let stringData = String(data: data, encoding: .utf8) else { - DefaultDiscordLogger.logger.error("Not string data? Response code: \(response.statusCode)", type: "JSON") + logger.error("Not string data? Response code: \(response.statusCode)") return nil } guard response.statusCode != 204 else { - DefaultDiscordLogger.logger.debug("Response code 204: No content", type: "JSON") + logger.debug("Response code 204: No content") return nil } guard response.statusCode == 200 || response.statusCode == 201 else { - DefaultDiscordLogger.logger.error("Invalid response code \(response.statusCode)", type: "JSON") - DefaultDiscordLogger.logger.error("Response: \(stringData)", type: "JSON") + logger.error("Invalid response code \(response.statusCode)") + logger.error("Response: \(stringData)") return nil } diff --git a/Sources/SwiftDiscord/DiscordLogger.swift b/Sources/SwiftDiscord/DiscordLogger.swift deleted file mode 100644 index e2ca51a2d..000000000 --- a/Sources/SwiftDiscord/DiscordLogger.swift +++ /dev/null @@ -1,83 +0,0 @@ -// The MIT License (MIT) -// Copyright (c) 2016 Erik Little - -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without -// limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the -// Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -// Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO -// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -import Foundation -import Logging - -/// Declares that a type will act as a logger. -public protocol DiscordLogger { - // MARK: Properties - - /// Whether to log or not. - var level: Logger.Level { get set } - - // MARK: Methods - - /// Error Messages. - func error(_ message: @autoclosure () -> String, type: String) - - /// Normal log messages. - func log( _ message: @autoclosure () -> String, type: String) - - /// More info on log messages. - func verbose(_ message: @autoclosure () -> String, type: String) - - /// Debug messages. - func debug(_ message: @autoclosure () -> String, type: String) - - /// Trace Messages. - func trace(_ message: @autoclosure () -> String, type: String) -} - -class DefaultDiscordLogger : DiscordLogger { - static var logger: DiscordLogger = DefaultDiscordLogger() - - private var downstreamLogger = Logger(label: "SwiftDiscord") - var level: Logger.Level { - get { return downstreamLogger.logLevel } - set { downstreamLogger.logLevel = newValue } - } - - /// Error Messages. - func error(_ message: @autoclosure () -> String, type: String) { - abstractLog(.error, message: message(), type: type) - } - - /// Normal log messages. - func log(_ message: @autoclosure () -> String, type: String) { - abstractLog(.info, message: message(), type: type) - } - - /// More info on log messages. - func verbose(_ message: @autoclosure () -> String, type: String) { - abstractLog(.debug, message: message(), type: type) - } - - /// Debug messages. - func debug(_ message: @autoclosure () -> String, type: String) { - abstractLog(.debug, message: message(), type: type) - } - - /// Trace Messages. - func trace(_ message: @autoclosure () -> String, type: String) { - abstractLog(.trace, message: message(), type: type) - } - - private func abstractLog(_ level: Logger.Level, message: String, type: String) { - downstreamLogger.log(level: level, "\(type): \(message)") - } -} diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift index ad82dba89..a4a91c8cf 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift @@ -17,6 +17,7 @@ import Foundation import NIO +import Logging import WebSocketKit import Dispatch @@ -28,6 +29,8 @@ private let os = "iOS" private let os = "Linux" #endif +fileprivate let logger = Logger(label: "DiscordEngine") + /// /// The base class for Discord WebSocket communications. /// @@ -146,7 +149,7 @@ open class DiscordEngine : DiscordEngineSpec { /// Disconnects the engine. An `engine.disconnect` is fired on disconnection. /// public func disconnect() { - DefaultDiscordLogger.logger.log("Disconnecting, \(description)", type: "DiscordWebSocketable") + logger.info("Disconnecting, \(description)") closed = true @@ -163,7 +166,7 @@ open class DiscordEngine : DiscordEngineSpec { connected = false - DefaultDiscordLogger.logger.log("Disconnected, shard: \(shardNum)", type: logType) + logger.info("Disconnected, shard: \(shardNum)") if closeReason == .sessionTimeout { sessionId = nil @@ -185,7 +188,7 @@ open class DiscordEngine : DiscordEngineSpec { /// open func handleDispatch(_ payload: DiscordGatewayPayload) { guard let type = payload.name, let event = DiscordDispatchEvent(rawValue: type) else { - DefaultDiscordLogger.logger.error("Could not create dispatch event \(payload)", type: logType) + logger.error("Could not create dispatch event \(payload)") return } @@ -217,9 +220,9 @@ open class DiscordEngine : DiscordEngineSpec { func _handleGatewayPayload(_ payload: DiscordGatewayPayload) { func handleInvalidSession() { if case let .bool(netsplit) = payload.payload, netsplit { - DefaultDiscordLogger.logger.log("Netsplit recieved, trying to resume", type: logType) + logger.info("Netsplit recieved, trying to resume") } else { - DefaultDiscordLogger.logger.log("Invalid session received. Invalidating session", type: logType) + logger.info("Invalid session received. Invalidating session") sessionId = nil } @@ -247,7 +250,7 @@ open class DiscordEngine : DiscordEngineSpec { sendPayload(DiscordGatewayPayload(code: .gateway(.heartbeat), payload: .integer(lastSequenceNumber))) case .heartbeatAck: heartbeatQueue.sync { self.pongsMissed = 0 } - DefaultDiscordLogger.logger.debug("Got heartbeat ack", type: logType) + logger.debug("Got heartbeat ack") default: error(message: "Unhandled payload: \(payload.code)") } @@ -275,7 +278,7 @@ open class DiscordEngine : DiscordEngineSpec { /// Handles the resumed event. You shouldn't call this directly. /// open func handleResumed(_ payload: DiscordGatewayPayload) { - DefaultDiscordLogger.logger.log("Resumed gateway session on shard: \(shardNum)", type: logType) + logger.info("Resumed gateway session on shard: \(shardNum)") heartbeatQueue.sync { self.pongsMissed = 0 } resuming = false @@ -306,12 +309,12 @@ open class DiscordEngine : DiscordEngineSpec { /// open func resumeGateway() { guard !resuming && !closed else { - DefaultDiscordLogger.logger.log("Already trying to resume or closed, ignoring", type: logType) + logger.info("Already trying to resume or closed, ignoring") return } - DefaultDiscordLogger.logger.log("Trying to resume gateway session on shard: \(shardNum)", type: logType) + logger.info("Trying to resume gateway session on shard: \(shardNum)") resuming = true @@ -322,8 +325,7 @@ open class DiscordEngine : DiscordEngineSpec { handleQueue.asyncAfter(deadline: DispatchTime.now() + Double(wait)) {[weak self] in guard let this = self, this.resuming else { return } - DefaultDiscordLogger.logger.debug("Calling engine connect for gateway resume with wait: \(wait)", - type: this.logType) + logger.debug("Calling engine connect for gateway resume with wait: \(wait)") this.connect() this._resumeGateway(wait: 10) @@ -337,14 +339,13 @@ open class DiscordEngine : DiscordEngineSpec { /// open func sendHeartbeat() { guard connected else { - DefaultDiscordLogger.logger.error("Tried heartbeating on disconnected shard, shard: \(shardNum)", - type: logType) + logger.error("Tried heartbeating on disconnected shard, shard: \(shardNum)") return } guard pongsMissed < 2 else { - DefaultDiscordLogger.logger.log("Too many pongs missed; closing, shard: \(shardNum)", type: logType) + logger.info("Too many pongs missed; closing, shard: \(shardNum)") pongsMissed = 0 closeWebSockets(fast: true) @@ -352,7 +353,7 @@ open class DiscordEngine : DiscordEngineSpec { return } - DefaultDiscordLogger.logger.debug("Sending heartbeat, shard: \(shardNum)", type: logType) + logger.debug("Sending heartbeat, shard: \(shardNum)") pongsMissed += 1 sendPayload(DiscordGatewayPayload(code: .gateway(.heartbeat), payload: .integer(lastSequenceNumber))) @@ -379,11 +380,11 @@ open class DiscordEngine : DiscordEngineSpec { } if sessionId != nil { - DefaultDiscordLogger.logger.log("Sending resume, shard: \(shardNum)", type: logType) + logger.info("Sending resume, shard: \(shardNum)") sendPayload(DiscordGatewayPayload(code: .gateway(.resume), payload: .object(resumeObject))) } else { - DefaultDiscordLogger.logger.log("Sending handshake, shard: \(shardNum)", type: logType) + logger.info("Sending handshake, shard: \(shardNum)") sendPayload(DiscordGatewayPayload(code: .gateway(.identify), payload: .object(handshakeObject))) } @@ -395,7 +396,7 @@ open class DiscordEngine : DiscordEngineSpec { /// - parameter milliseconds: The heartbeat interval /// public func startHeartbeat(milliseconds: Int) { - DefaultDiscordLogger.logger.debug("Starting heartbeat, shard: \(shardNum), \(milliseconds)ms", type: logType) + logger.debug("Starting heartbeat, shard: \(shardNum), \(milliseconds)ms") heartbeatInterval = milliseconds diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift index ed0317b42..16c8137ed 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngineSpec.swift @@ -17,9 +17,12 @@ import Dispatch import Foundation +import Logging import NIO import WebSocketKit +fileprivate let logger = Logger(label: "DiscordEngineSpec") + /// Declares that a type will be an Engine for the Discord Gateway. public protocol DiscordEngineSpec : DiscordShard { // MARK: Properties @@ -83,7 +86,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu websocket?.onText { [weak self] ws, text in guard let this = self else { return } - DefaultDiscordLogger.logger.debug("\(this.description), Got text: \(text)", type: "DiscordWebSocketable") + logger.debug("\(this.description), Got text: \(text)") this.parseGatewayMessage(text) } @@ -91,7 +94,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu websocket?.onClose.whenSuccess { [weak self] in guard let this = self else { return } - DefaultDiscordLogger.logger.log("Websocket closed, \(this.description)", type: "DiscordWebSocketable") + logger.info("Websocket closed, \(this.description)") this.handleClose(reason: nil) } @@ -99,7 +102,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu websocket?.onClose.whenFailure { [weak self] err in guard let this = self else { return } - DefaultDiscordLogger.logger.log("WebSocket errored: \(err), \(this.description);", type: "DiscordWebSocketable") + logger.info("WebSocket errored: \(err), \(this.description);") this.handleClose(reason: nil) } @@ -113,8 +116,8 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu } private func _connect() { - DefaultDiscordLogger.logger.log("Connecting to \(connectURL), \(description)", type: "DiscordWebSocketable") - DefaultDiscordLogger.logger.log("Attaching WebSocket, shard: \(description)", type: "DiscordWebSocketable") + logger.info("Connecting to \(connectURL), \(description)") + logger.info("Attaching WebSocket, shard: \(description)") let url = URL(string: connectURL)! let path = url.path.isEmpty ? "/" : url.path @@ -132,7 +135,7 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu ) { [weak self] ws in guard let this = self else { return } - DefaultDiscordLogger.logger.log("Websocket connected, shard: \(this.description)", type: "DiscordWebSocketable") + logger.info("Websocket connected, shard: \(this.description)") this.websocket = ws this.connectUUID = UUID() @@ -144,14 +147,14 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu future.whenFailure { [weak self] err in guard let this = self else { return } - DefaultDiscordLogger.logger.log("Websocket errored, closing: \(err), \(this.description)", type: "DiscordWebSocketable") + logger.info("Websocket errored, closing: \(err), \(this.description)") this.handleClose(reason: err) } } internal func closeWebSockets(fast: Bool = false) { - DefaultDiscordLogger.logger.log("Closing WebSocket, shard: \(description)", type: "DiscordWebSocketable") + logger.info("Closing WebSocket, shard: \(description)") guard !fast else { handleClose(reason: nil) @@ -168,6 +171,6 @@ public extension DiscordWebSocketable where Self: DiscordGatewayable & DiscordRu /// - parameter message: The error message /// func error(message: String) { - DefaultDiscordLogger.logger.error(message, type: description) + logger.error("\(message)") } } diff --git a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift index edf19c7a3..5fd34fb63 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordGateway.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordGateway.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +import Logging + +fileprivate let logger = Logger(label: "DiscordGateway") /// Declares that a type will communicate with a Discord gateway. public protocol DiscordGatewayable : DiscordEngineHeartbeatable { @@ -97,7 +100,7 @@ public extension DiscordGatewayable where Self: DiscordWebSocketable & DiscordRu return } - DefaultDiscordLogger.logger.debug("Sending ws: \(payloadString)", type: description) + logger.debug("Sending ws: \(payloadString)") runloop.execute { self.websocket?.send(payloadString) diff --git a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift index 263e143c5..855b20e0f 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift @@ -17,8 +17,11 @@ import Dispatch import Foundation +import Logging import NIO +fileprivate let logger = Logger(label: "DiscordSharding") + /// Struct that represents shard information. /// Used when a client is doing manual sharding. public struct DiscordShardInformation { @@ -258,7 +261,7 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { open func manuallyShatter(withInfo info: DiscordShardInformation) { guard let delegate = self.delegate else { return } - DefaultDiscordLogger.logger.verbose("Handling shard range \(info.shardRange)", type: "DiscordShardManager") + logger.debug("(verbose) Handling shard range \(info.shardRange)") cleanUp() @@ -310,7 +313,7 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { /// - parameter shardNum: The number of the shard that disconnected. /// open func shardDidConnect(_ shard: DiscordShard) { - DefaultDiscordLogger.logger.verbose("Shard #\(shard.shardNum), connected", type: "DiscordShardManager") + logger.debug("(verbose) Shard #\(shard.shardNum), connected") protected { connectedShards += 1 } @@ -325,7 +328,7 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { /// - parameter shardNum: The number of the shard that disconnected. /// open func shardDidDisconnect(_ shard: DiscordShard) { - DefaultDiscordLogger.logger.verbose("Shard #\(shard.shardNum), disconnected", type: "DiscordShardManager") + logger.debug("(verbose) Shard #\(shard.shardNum), disconnected") protected { closedShards += 1 } diff --git a/Sources/SwiftDiscord/Guild/DiscordEmoji.swift b/Sources/SwiftDiscord/Guild/DiscordEmoji.swift index cbe5123c0..2fb04b99d 100644 --- a/Sources/SwiftDiscord/Guild/DiscordEmoji.swift +++ b/Sources/SwiftDiscord/Guild/DiscordEmoji.swift @@ -15,6 +15,10 @@ // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +import Logging + +fileprivate let logger = Logger(label: "DiscordEmoji") + /// Represents an Emoji. public struct DiscordEmoji { // MARK: Properties @@ -50,7 +54,7 @@ public struct DiscordEmoji { if let emojiID = emoji.id { emojis[emojiID] = emoji } else { - DefaultDiscordLogger.logger.debug("EmojisFromArray used on array with non-custom emoji", type: "DiscordEmoji") + logger.debug("EmojisFromArray used on array with non-custom emoji") } } diff --git a/Sources/SwiftDiscord/Guild/DiscordGuild.swift b/Sources/SwiftDiscord/Guild/DiscordGuild.swift index 7f74aea2b..bb00ddc44 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuild.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuild.swift @@ -20,11 +20,12 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif +import Logging + +fileprivate let logger = Logger(label: "DiscordGuild") /// Represents a Guild. public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { - private static let logType = "DiscordGuild" - // MARK: Properties // TODO figure out what features are @@ -162,7 +163,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { public func createChannel(with options: [DiscordEndpoint.Options.GuildCreateChannel], reason: String? = nil) { guard let client = self.client else { return } - DefaultDiscordLogger.logger.log("Creating guild channel on \(id)", type: "DiscordGuild") + logger.info("Creating guild channel on \(id)") client.createGuildChannel(on: id, options: options, reason: reason) } @@ -204,7 +205,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { guard let client = self.client else { return callback(nil, nil) } client.getGuildMember(by: userId, on: id) {member, response in - DefaultDiscordLogger.logger.debug("Got member: \(userId)", type: "DiscordGuild") + logger.debug("Got member: \(userId)") var member = member member?.guild = self @@ -276,12 +277,12 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { let userId = presence.user.id if pruneUsers && presence.status == .offline { - DefaultDiscordLogger.logger.debug("Pruning guild member \(userId) on \(id)", type: DiscordGuild.logType) + logger.debug("Pruning guild member \(userId) on \(id)") members[userId] = nil presences[userId] = nil } else if fillUsers && !members.contains(userId) { - DefaultDiscordLogger.logger.debug("Should get member \(userId); pull from the API", type: DiscordGuild.logType) + logger.debug("Should get member \(userId); pull from the API") members[lazy: userId] = .lazy({[weak self] in guard let this = self else { @@ -355,7 +356,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { public func unban(_ user: DiscordUser) { guard let client = self.client else { return } - DefaultDiscordLogger.logger.log("Unbanning user \(user) on \(id)", type: "DiscordGuild") + logger.info("Unbanning user \(user) on \(id)") client.removeGuildBan(for: user.id, on: id) } diff --git a/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift b/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift index 16aa3053e..719616855 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift @@ -15,6 +15,10 @@ // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +import Logging + +fileprivate let logger = Logger(label: "DiscordGuildChannel") + /// Protocol that declares a type will be a Discord guild channel. public protocol DiscordGuildChannel : DiscordChannel { /// The snowflake id of the guild this channel is on. @@ -153,8 +157,7 @@ func guildChannel(fromObject channelObject: [String: Any], case .category: return DiscordGuildChannelCategory(categoryObject: channelObject, guildID: guildID, client: client) default: - DefaultDiscordLogger.logger.error("Unhandled guild channel in guildChannelFromObject", - type: "DiscordGuildChannel") + logger.error("Unhandled guild channel in guildChannelFromObject") return nil } } diff --git a/Sources/SwiftDiscord/Guild/DiscordGuildMember.swift b/Sources/SwiftDiscord/Guild/DiscordGuildMember.swift index 965140a18..d5b2005ab 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuildMember.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuildMember.swift @@ -16,6 +16,9 @@ // DEALINGS IN THE SOFTWARE. import Foundation +import Logging + +fileprivate let logger = Logger(label: "DiscordGuildMember") /// Represents a guild member. public struct DiscordGuildMember { @@ -115,7 +118,7 @@ public struct DiscordGuildMember { for guildMember in guildMembersArray { guard let user = guildMember["user"] as? [String: Any], let id = Snowflake(user["id"] as? String) else { - DefaultDiscordLogger.logger.error("Couldn't extract userId from user JSON", type: "GuildMembersFromArray") + logger.error("Couldn't extract userId from user JSON") continue } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 9afd8bae5..2ab53c346 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -19,6 +19,9 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif +import Logging + +fileprivate let logger = Logger(label: "DiscordEndpoint") // TODO Group DM // TODO Add guild member @@ -457,15 +460,13 @@ public extension DiscordEndpoint { private func createURL(getParams: [String: String]?) -> URL? { // This can fail, specifically if you try to include a non-url-encoded emoji in it guard let url = URL(string: self.combined) else { - DefaultDiscordLogger.logger.error("Couldn't convert \"\(self.combined)\" to a URL. This shouldn't happen.", - type: "DiscordEndpoint") + logger.error("Couldn't convert \"\(self.combined)\" to a URL. This shouldn't happen.") return nil } guard let getParams = getParams else { return url } guard var com = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - DefaultDiscordLogger.logger.error("Couldn't convert \"\(url)\" to URLComponents. This shouldn't happen.", - type: "DiscordEndpoint") + logger.error("Couldn't convert \"\(url)\" to URLComponents. This shouldn't happen.") return nil } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift index bb27a8335..fdc36817f 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift @@ -19,6 +19,9 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif +import Logging + +fileprivate let logger = Logger(label: "DiscordEndpointChannels") public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation @@ -342,8 +345,8 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { extraHeaders: nil) } - DefaultDiscordLogger.logger.log("Sending message to: \(channelId)", type: "DiscordEndpointChannels") - DefaultDiscordLogger.logger.verbose("Message: \(message)", type: "DiscordEndpointChannels") + logger.info("Sending message to: \(channelId)") + logger.debug("(verbose) Message: \(message)") let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(message)? = JSON.jsonFromResponse(data: data, response: response) else { diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift index 2f4c01405..a40e1b4db 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift @@ -19,6 +19,9 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif +import Logging + +fileprivate let logger = Logger(label: "DiscordEndpointGuild") public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation @@ -111,8 +114,8 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } } - DefaultDiscordLogger.logger.log("Creating a new role on \(guildId)", type: "DiscordEndpointGuild") - DefaultDiscordLogger.logger.verbose("Role options \(roleData)", type: "DiscordEndpointGuild") + logger.info("Creating a new role on \(guildId)") + logger.debug("(verbose) Role options \(roleData)") guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(roleData)) else { return callback(nil, nil) } @@ -155,7 +158,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { func getGuildAuditLog(for guildId: GuildID, withOptions options: [DiscordEndpoint.Options.AuditLog], callback: @escaping (DiscordAuditLog?, HTTPURLResponse?) -> ()) { - DefaultDiscordLogger.logger.debug("Getting audit log for \(guildId)", type: "DiscordEndpointGuild") + logger.debug("Getting audit log for \(guildId)") var getParams = [String: String]() @@ -181,7 +184,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { return } - DefaultDiscordLogger.logger.debug("Got audit log for \(guildId)", type: "DiscordEndpointGuild") + logger.debug("Got audit log for \(guildId)") callback(DiscordAuditLog(auditLogObject: log), response) } @@ -202,7 +205,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { return } - DefaultDiscordLogger.logger.debug("Got guild bans \(bans)", type: "DiscordEndpointGuild") + logger.debug("Got guild bans \(bans)") callback(DiscordBan.bansFromArray(bans as! [[String: Any]]), response) } @@ -421,8 +424,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(patchParams)) else { return } - DefaultDiscordLogger.logger.debug("Modifying guild member \(id) with options: \(patchParams) on \(guildId)", - type: "DiscordEndpointGuild") + logger.debug("Modifying guild member \(id) with options: \(patchParams) on \(guildId)") rateLimiter.executeRequest(endpoint: .guildMember(guild: guildId, user: id), token: token, @@ -463,7 +465,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { on guildId: GuildID, reason: String? = nil, callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { - DefaultDiscordLogger.logger.log("Unbanning \(userId) on \(guildId)", type: "DiscordEndpointGuild") + logger.info("Unbanning \(userId) on \(guildId)") var extraHeaders = [DiscordHeader: String]() diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift index 6a6b019dd..b0c6ee360 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+User.swift @@ -19,6 +19,9 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif +import Logging + +fileprivate let logger = Logger(label: "DiscordEndpointUser") public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation @@ -51,7 +54,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { return } - DefaultDiscordLogger.logger.debug("Got DMChannels: \(channels)", type: "DiscordEndpointUser") + logger.debug("Got DMChannels: \(channels)") callback(DiscordDMChannel.DMsfromArray(channels as! [[String: Any]]), response) } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift index 298a6c653..ebf968d8a 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Webhooks.swift @@ -19,6 +19,9 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif +import Logging + +fileprivate let logger = Logger(label: "DiscordEndpointWebhooks") public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation @@ -42,7 +45,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } } - DefaultDiscordLogger.logger.debug("Creating webhook on: \(channelId)", type: "DiscordEndpointChannels") + logger.debug("Creating webhook on: \(channelId)") guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(createJSON)) else { return } @@ -119,7 +122,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation func getWebhooks(forGuild guildId: GuildID, callback: @escaping ([DiscordWebhook], HTTPURLResponse?) -> ()) { - DefaultDiscordLogger.logger.debug("Getting webhooks for guild: \(guildId)", type: "DiscordEndpointWebhooks") + logger.debug("Getting webhooks for guild: \(guildId)") let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(webhooks)? = JSON.jsonFromResponse(data: data, response: response) else { @@ -160,7 +163,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(createJSON)) else { return } - DefaultDiscordLogger.logger.debug("Modifying webhook: \(webhookId)", type: "DiscordEndpointChannels") + logger.debug("Modifying webhook: \(webhookId)") let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(webhook)? = JSON.jsonFromResponse(data: data, response: response) else { diff --git a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift index b2d55fba1..448eb7f09 100644 --- a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift +++ b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift @@ -21,10 +21,13 @@ import FoundationNetworking #endif import Dispatch +import Logging internal typealias DiscordRequestCallback = (Data?, HTTPURLResponse?, Error?) -> () private typealias RateLimitedRequest = (request: URLRequest, callback: DiscordRequestCallback) +fileprivate let logger = Logger(label: "DiscordRateLimiter") + /// The DiscordRateLimiter is in charge of making sure we don't flood Discord with requests. /// It keeps a dictionary of DiscordRateLimitKeys and DiscordRateLimits. /// All requests to the REST api should be routed through the DiscordRateLimiter. @@ -73,7 +76,7 @@ public final class DiscordRateLimiter : DiscordRateLimiterSpec { let rateLimit = endpointLimits[endpointKey]! if rateLimit.atLimit { - DefaultDiscordLogger.logger.debug("Hit rate limit: \(rateLimit)", type: "DiscordRateLimiter") + logger.debug("Hit rate limit: \(rateLimit)") guard !failFast else { callbackQueue.async { callback(nil, nil, nil) } @@ -89,7 +92,7 @@ public final class DiscordRateLimiter : DiscordRateLimiterSpec { rateLimit.remaining -= 1 - DefaultDiscordLogger.logger.debug("Doing request: \(request), remaining: \(rateLimit.remaining)", type: "DiscordRateLimiter") + logger.debug("Doing request: \(request), remaining: \(rateLimit.remaining)") session.dataTask(with: request, completionHandler: createResponseHandler(for: request, endpointKey: endpointKey, @@ -157,9 +160,9 @@ public final class DiscordRateLimiter : DiscordRateLimiterSpec { reset: Int(reset as! String)!) } - DefaultDiscordLogger.logger.debug("New limit: \(rateLimit.limit)", type: "DiscordRateLimiter") - DefaultDiscordLogger.logger.debug("New remaining: \(rateLimit.remaining)", type: "DiscordRateLimiter") - DefaultDiscordLogger.logger.debug("New reset: \(rateLimit.reset)", type: "DiscordRateLimiter") + logger.debug("New limit: \(rateLimit.limit)") + logger.debug("New remaining: \(rateLimit.remaining)") + logger.debug("New reset: \(rateLimit.reset)") callbackQueue.async { callback(data, response, error) } } @@ -327,7 +330,7 @@ private final class DiscordRateLimit { scheduledReset = true queue.asyncAfter(deadline: deadlineForReset) { - DefaultDiscordLogger.logger.debug("Reset triggered: \(self.endpointKey)", type: "RateLimit") + logger.debug("Reset triggered: \(self.endpointKey)") self.remaining = self.limit self.scheduledReset = false @@ -343,7 +346,7 @@ private final class DiscordRateLimit { removed += 1 } while removed < self.remaining && self.queue.count != 0 - DefaultDiscordLogger.logger.debug("Sent \(removed) requests for limit: \(self.endpointKey)", type: "RateLimit") + logger.debug("Sent \(removed) requests for limit: \(self.endpointKey)") } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift index 528e763cb..81ec42e74 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift @@ -18,6 +18,9 @@ import COPUS import Dispatch import Foundation +import Logging + +fileprivate let logger = Logger(label: "DiscordVoiceDataSource") /// Specifies that a type will be a data source for a VoiceEngine. public protocol DiscordVoiceDataSource : class { @@ -79,8 +82,6 @@ public enum DiscordVoiceDataSourceStatus : Error { open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { // MARK: Properties - private static let logType = "DiscordBufferedVoiceDataSource" - /// The max number of voice packets to buffer. /// Roughly equal to `(nPackets * 20ms) / 1000 = seconds to buffer`. public let bufferSize: Int @@ -139,7 +140,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { } deinit { - DefaultDiscordLogger.logger.debug("deinit", type: DiscordBufferedVoiceDataSource.logType) + logger.debug("deinit") guard !closed else { return } @@ -167,7 +168,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { open func createDispatchIO() { self.source = DispatchIO(type: .stream, fileDescriptor: pipe.fileHandleForReading.fileDescriptor, queue: encoderQueue, cleanupHandler: {code in - DefaultDiscordLogger.logger.debug("Source spent: \(code)", type: DiscordBufferedVoiceDataSource.logType) + logger.debug("Source spent: \(code)") }) } @@ -185,12 +186,11 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { encoderQueue.sync { done = self.done - DefaultDiscordLogger.logger.trace("Buffer state: count: \(self.readBuffer.count) drain: \(self.drain)", - type: DiscordBufferedVoiceDataSource.logType) + logger.trace("Buffer state: count: \(self.readBuffer.count) drain: \(self.drain)") if self.drain && self.readBuffer.count <= self.drainThreshold { // The swamp has been drained, start reading again - DefaultDiscordLogger.logger.debug("Buffer drained, scheduling read", type: DiscordBufferedVoiceDataSource.logType) + logger.debug("Buffer drained, scheduling read") self.drain = false self.startReading() @@ -217,7 +217,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { open func finishUpAndClose() { guard !closed else { return } - DefaultDiscordLogger.logger.debug("Closing pipe for writing", type: DiscordBufferedVoiceDataSource.logType) + logger.debug("Closing pipe for writing") writeToHandler.closeFile() @@ -243,23 +243,21 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { guard let this = self else { return } guard let data = data, data.count > 0 else { - DefaultDiscordLogger.logger.debug("No data, reader probably closed", - type: DiscordBufferedVoiceDataSource.logType) + logger.debug("No data, reader probably closed") this.done = true if done && code == 0 { // EOF reached - DefaultDiscordLogger.logger.debug("Reader done", type: DiscordBufferedVoiceDataSource.logType) + logger.debug("Reader done") } else { - DefaultDiscordLogger.logger.debug("Something is weird \(done) \(code)", - type: DiscordBufferedVoiceDataSource.logType) + logger.debug("Something is weird \(done) \(code)") } return } - DefaultDiscordLogger.logger.debug("Read \(data.count) bytes", type: DiscordBufferedVoiceDataSource.logType) + logger.debug("Read \(data.count) bytes") do { try data.withUnsafeBytes {(bytes: UnsafePointer) in @@ -269,8 +267,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { guard this.readBuffer.count < this.bufferSize else { // Buffer is full; wait till it's drained // Whatever is in charge of taking from the buffer should queue up more reading - DefaultDiscordLogger.logger.debug("Buffer full, not reading again", - type: DiscordBufferedVoiceDataSource.logType) + logger.debug("Buffer full, not reading again") this.drain = true return @@ -278,7 +275,7 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { this._read() } catch { - DefaultDiscordLogger.logger.error("Error encoding bytes", type: DiscordBufferedVoiceDataSource.logType) + logger.error("Error encoding bytes") } } } @@ -299,8 +296,6 @@ open class DiscordBufferedVoiceDataSource : DiscordVoiceDataSource { open class DiscordVoiceFileDataSource : DiscordBufferedVoiceDataSource { // MARK: Properties - private static let logType = "DiscordVoiceFileDataSource" - /// A FileHandle for reading the wrapped file. public let wrappedFile: FileHandle @@ -325,7 +320,7 @@ open class DiscordVoiceFileDataSource : DiscordBufferedVoiceDataSource { } deinit { - DefaultDiscordLogger.logger.debug("deinit", type: DiscordVoiceFileDataSource.logType) + logger.debug("deinit") } // MARK: Methods @@ -338,7 +333,7 @@ open class DiscordVoiceFileDataSource : DiscordBufferedVoiceDataSource { open override func createDispatchIO() { self.source = DispatchIO(type: .stream, fileDescriptor: wrappedFile.fileDescriptor, queue: encoderQueue, cleanupHandler: {code in - DefaultDiscordLogger.logger.debug("Source spent: \(code)", type: DiscordVoiceFileDataSource.logType) + logger.debug("Source spent: \(code)") }) } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift index f73fa27e1..7372b7c87 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDecoder.swift @@ -17,6 +17,9 @@ import COPUS import Foundation +import Logging + +fileprivate let logger = Logger(label: "DiscordVoiceDecoder") /// Class that decodes Opus voice data into raw PCM data for a VoiceEngine. It can decode multiple streams. Decoding is /// not thread safe, and it is up to the caller to decode safely. @@ -37,10 +40,10 @@ open class DiscordVoiceSessionDecoder { let decoder: DiscordOpusDecoder if let previous = decoders[packet.ssrc] { - DefaultDiscordLogger.logger.debug("Reusing decoder for ssrc: \(packet.ssrc), seqNum: \(packet.seqNum), timestamp: \(packet.timestamp)", type: "DiscordVoiceSessionDecoder") + logger.debug("Reusing decoder for ssrc: \(packet.ssrc), seqNum: \(packet.seqNum), timestamp: \(packet.timestamp)") decoder = previous } else { - DefaultDiscordLogger.logger.debug("New decoder for ssrc: \(packet.ssrc), seqNum: \(packet.seqNum), timestamp: \(packet.timestamp)", type: "DiscordVoiceSessionDecoder") + logger.debug("New decoder for ssrc: \(packet.ssrc), seqNum: \(packet.seqNum), timestamp: \(packet.timestamp)") decoder = try DiscordOpusDecoder(sampleRate: 48_000, channels: 2) decoders[packet.ssrc] = decoder } @@ -61,8 +64,8 @@ open class DiscordVoiceSessionDecoder { sequences[packet.ssrc] = packet.seqNum timestamps[packet.ssrc] = packet.timestamp - DefaultDiscordLogger.logger.debug("Out of order packet", type: "DiscordVoiceSessionDecoder") - DefaultDiscordLogger.logger.debug("Looks to have a sequence difference of \(packet.seqNum - previousSeqNum)", type: "DiscordVoiceSessionDecoder") + logger.debug("Out of order packet") + logger.debug("Looks to have a sequence difference of \(packet.seqNum - previousSeqNum)") for _ in 0.. (String, Int) { - DefaultDiscordLogger.logger.debug("Extracting ip and port from \(bytes)", type: DiscordVoiceEngine.logType) + logger.debug("Extracting ip and port from \(bytes)") let ipData = bytes.dropLast(2) let portBytes = Array(bytes.suffix(from: bytes.endIndex.advanced(by: -2))) @@ -329,18 +331,17 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { do { sendVoiceData(try source.engineNeedsData(self)) } catch DiscordVoiceDataSourceStatus.noData { - DefaultDiscordLogger.logger.trace("No data", type: DiscordVoiceEngine.logType) + logger.trace("No data") if speaking { sendSpeaking(false) } } catch DiscordVoiceDataSourceStatus.done { - DefaultDiscordLogger.logger.debug("Voice source done, sending silence", type: DiscordVoiceEngine.logType) + logger.debug("Voice source done, sending silence") sendSilence(previousSource: nil) } catch let DiscordVoiceDataSourceStatus.silenceDone(source) { - DefaultDiscordLogger.logger.debug("Voice silence done, requesting new source", - type: DiscordVoiceEngine.logType) + logger.debug("Voice silence done, requesting new source") if speaking { sendSpeaking(false) @@ -354,7 +355,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { self.source = source } } catch { - DefaultDiscordLogger.logger.error("Error getting voice data: \(error)", type: DiscordVoiceEngine.logType) + logger.error("Error getting voice data: \(error)") } } @@ -364,7 +365,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// - parameter reason: The reason the socket closed. /// public func handleClose(reason: Error? = nil) { - DefaultDiscordLogger.logger.log("Voice engine closed", type: DiscordVoiceEngine.logType) + logger.info("Voice engine closed") closeOutEngine() } @@ -378,12 +379,11 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// - parameter payload: The dispatch payload /// public func handleHello(_ payload: DiscordGatewayPayload) { - DefaultDiscordLogger.logger.debug("Handling hello \(payload)", type: DiscordVoiceEngine.logType) + logger.debug("Handling hello \(payload)") guard case let .object(helloPayload) = payload.payload, let heartbeat = helloPayload["heartbeat_interval"] as? Int else { - DefaultDiscordLogger.logger.error("Error extracting heartbeat info \(payload)", - type: DiscordVoiceEngine.logType) + logger.error("Error extracting heartbeat info \(payload)") return } @@ -412,18 +412,18 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { self.sendSilence(previousSource: self.source) } case .speaking: - DefaultDiscordLogger.logger.debug("Got speaking \(payload)", type: DiscordVoiceEngine.logType) + logger.debug("Got speaking \(payload)") case .hello: handleHello(payload) case .heartbeatAck: - DefaultDiscordLogger.logger.debug("Got heartbeat ack", type: DiscordVoiceEngine.logType) + logger.debug("Got heartbeat ack") case .resumed: handleResumed(payload) case .clientDisconnect: // Should we tell someone about this? - DefaultDiscordLogger.logger.debug("Someone left voice channel \(payload)", type: DiscordVoiceEngine.logType) + logger.debug("Someone left voice channel \(payload)") default: - DefaultDiscordLogger.logger.debug("Unhandled voice payload \(payload)", type: DiscordVoiceEngine.logType) + logger.debug("Unhandled voice payload \(payload)") } } @@ -453,7 +453,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// public func handleResumed(_ payload: DiscordGatewayPayload) { // TODO implement voice resume - DefaultDiscordLogger.logger.debug("Should handle resumed \(payload)", type: DiscordVoiceEngine.logType) + logger.debug("Should handle resumed \(payload)") } private func handleVoiceSessionDescription(with payload: DiscordGatewayPayloadData) { @@ -473,7 +473,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// public func parseGatewayMessage(_ string: String) { guard let decoded = DiscordGatewayPayload.payloadFromString(string, fromGateway: false) else { - DefaultDiscordLogger.logger.log("Got unknown payload \(string)", type: DiscordVoiceEngine.logType) + logger.info("Got unknown payload \(string)") return } @@ -495,7 +495,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { _ = try socket.readDatagram(into: &data) - DefaultDiscordLogger.logger.debug("Received data \(data)", type: "DiscordVoiceEngine") + logger.debug("Received data \(data)") guard let this = self else { return } @@ -508,9 +508,9 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { this.voiceDelegate?.voiceEngine(this, didReceiveOpusVoiceData: packet) } } catch DiscordVoiceError.initialPacket { - DefaultDiscordLogger.logger.debug("Got initial packet", type: DiscordVoiceEngine.logType) + logger.debug("Got initial packet") } catch DiscordVoiceError.decodeFail { - DefaultDiscordLogger.logger.debug("Failed to decode a packet", type: DiscordVoiceEngine.logType) + logger.debug("Failed to decode a packet") } catch EngineError.decryptionError { self?.error(message: "Error decrypting voice packet") } catch let err { @@ -536,8 +536,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { // currently only xsalsa20_poly1305 is supported // After this point we are good to go in sending encrypted voice packets private func selectProtocol(with ip: String, on port: Int) { - DefaultDiscordLogger.logger.debug("Selecting UDP protocol with ip: \(ip) on port: \(port)", - type: DiscordVoiceEngine.logType) + logger.debug("Selecting UDP protocol with ip: \(ip) on port: \(port)") let payloadData: [String: Any] = [ "protocol": "udp", @@ -555,7 +554,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { // which will cause an ask for a new source readSource() - DefaultDiscordLogger.logger.debug("VoiceEngine is ready!", type: DiscordVoiceEngine.logType) + logger.debug("VoiceEngine is ready!") guard config.captureVoice else { return } @@ -611,8 +610,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { sendSpeaking(true) } - DefaultDiscordLogger.logger.trace("Should send voice data: \(data.count) bytes", - type: DiscordVoiceEngine.logType) + logger.trace("Should send voice data: \(data.count) bytes") do { try udpSocket.write(from: Data(createVoicePacket(data))) @@ -652,7 +650,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { /// - parameter terminationHandler: Called when the middleware is done. Does not mean that all encoding is done. /// public func setupMiddleware(_ middleware: Process, terminationHandler: (() -> ())?) { - DefaultDiscordLogger.logger.debug("Setting up middleware", type: DiscordVoiceEngine.logType) + logger.debug("Setting up middleware") // TODO this is bad, fix the types here guard let source = self.source as? DiscordBufferedVoiceDataSource else { return } @@ -663,7 +661,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { do { try source.middleware?.start() } catch { - DefaultDiscordLogger.logger.error("Could not start middleware: \(error)", type: DiscordVoiceEngine.logType) + logger.error("Could not start middleware: \(error)") } } #endif @@ -674,7 +672,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { public func startHandshake() { guard voiceDelegate != nil else { return } - DefaultDiscordLogger.logger.log("Starting voice handshake", type: DiscordVoiceEngine.logType) + logger.info("Starting voice handshake") sendPayload(DiscordGatewayPayload(code: .voice(.identify), payload: .object(handshakeObject))) } @@ -693,7 +691,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { private func startUDP() { guard udpPort != -1, !udpIp.isEmpty else { return } - DefaultDiscordLogger.logger.debug("Starting voice UDP connection", type: DiscordVoiceEngine.logType) + logger.debug("Starting voice UDP connection") do { guard let sig = try Socket.Signature( @@ -712,7 +710,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { findIP() } catch let err { // TODO Handle voice error disconnect from voice - DefaultDiscordLogger.logger.error("UDP setup error \(err)", type: DiscordVoiceEngine.logType) + logger.error("UDP setup error \(err)") } } } diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift index 075532dea..e7b6d9175 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceManager.swift @@ -17,6 +17,9 @@ import Dispatch import Foundation +import Logging + +fileprivate let logger = Logger(label: "DiscordVoiceManager") /// A delegate for a VoiceManager. public protocol DiscordVoiceManagerDelegate : AnyObject, DiscordTokenBearer, DiscordEventLoopGroupManager { @@ -116,7 +119,7 @@ open class DiscordVoiceManager : DiscordVoiceEngineDelegate, Lockable { /// open func leaveVoiceChannel(onGuild guildId: GuildID) { guard let engine = get(voiceEngines[guildId]) else { - DefaultDiscordLogger.logger.error("Could not find a voice engine for guild \(guildId)", type: logType) + logger.error("Could not find a voice engine for guild \(guildId)") return } @@ -126,13 +129,13 @@ open class DiscordVoiceManager : DiscordVoiceEngineDelegate, Lockable { voiceServerInformations[guildId] = nil } - DefaultDiscordLogger.logger.verbose("Disconnecting voice engine for guild \(guildId)", type: logType) + logger.debug("(verbose) Disconnecting voice engine for guild \(guildId)") engine.disconnect() // Make sure everything is cleaned out - DefaultDiscordLogger.logger.verbose("Rejoining voice channels after leave", type: logType) + logger.debug("(verbose) Rejoining voice channels after leave") for (guildId, _) in voiceEngines { startVoiceConnection(guildId) @@ -173,7 +176,7 @@ open class DiscordVoiceManager : DiscordVoiceEngineDelegate, Lockable { secret: previousEngine?.secret ) - DefaultDiscordLogger.logger.log("Connecting voice engine", type: logType) + logger.info("Connecting voice engine") DispatchQueue.global().async {[weak engine = voiceEngines[guildId]!] in engine?.connect() From 778c5dc7ff11a72a8f2026e122c0f96d1bc8d8b8 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 22 Apr 2020 18:59:39 +0200 Subject: [PATCH 039/104] Add support for animated emojis --- Sources/SwiftDiscord/Guild/DiscordEmoji.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SwiftDiscord/Guild/DiscordEmoji.swift b/Sources/SwiftDiscord/Guild/DiscordEmoji.swift index 2fb04b99d..3fdca1393 100644 --- a/Sources/SwiftDiscord/Guild/DiscordEmoji.swift +++ b/Sources/SwiftDiscord/Guild/DiscordEmoji.swift @@ -28,6 +28,9 @@ public struct DiscordEmoji { /// Whether this is a managed emoji. public let managed: Bool + + /// Whether this is an animated emoji. + public let animated: Bool /// The name of the emoji or unicode representation if it's a unicode emoji. public let name: String @@ -41,6 +44,7 @@ public struct DiscordEmoji { init(emojiObject: [String: Any]) { id = Snowflake(emojiObject["id"] as? String) managed = emojiObject.get("managed", or: false) + animated = emojiObject.get("animated", or: false) name = emojiObject.get("name", or: "") requireColons = emojiObject.get("require_colons", or: false) roles = (emojiObject["roles"] as? [String])?.compactMap(Snowflake.init) ?? [] From 9e1d3bbaecf4806f7b60b69e7540aebb1b5eb103 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 26 May 2020 17:39:44 +0200 Subject: [PATCH 040/104] Update WebSocketKit to 2.1.0 --- Package.resolved | 8 ++++---- Package.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Package.resolved b/Package.resolved index 615d695ae..456ab6da4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "ff01888051cd7efceb1bf8319c1dd3986c4bf6fc", - "version": "2.10.1" + "revision": "c5fa0b456524cd73dc3ddbb263d4f46c20b86ca3", + "version": "2.17.0" } }, { @@ -60,8 +60,8 @@ "repositoryURL": "https://github.com/vapor/websocket-kit", "state": { "branch": null, - "revision": "66c0ea58398f055b5a0d92b0d5f4c32ef0c02eeb", - "version": null + "revision": "021edd1ca55451ad15b3e84da6b4064e4b877b34", + "version": "2.1.0" } } ] diff --git a/Package.swift b/Package.swift index a63849c32..389a949bc 100644 --- a/Package.swift +++ b/Package.swift @@ -20,7 +20,7 @@ import PackageDescription var deps: [Package.Dependency] = [ - .package(url: "https://github.com/vapor/websocket-kit", .revision("66c0ea58398f055b5a0d92b0d5f4c32ef0c02eeb")), + .package(url: "https://github.com/vapor/websocket-kit", .upToNextMinor(from: "2.1.0")), .package(url: "https://github.com/IBM-Swift/BlueSocket", .upToNextMinor(from: "1.0.0")), .package(url: "https://github.com/nuclearace/copus", .upToNextMinor(from: "2.1.1")), .package(url: "https://github.com/nuclearace/Sodium", .upToNextMinor(from: "2.0.0")), From a6776f05b97bcaf8824f50bba0d5cd03c6bcf56c Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 26 May 2020 18:07:37 +0200 Subject: [PATCH 041/104] Require macOS 10.15 as websocket-kit does --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index 389a949bc..6c71c2393 100644 --- a/Package.swift +++ b/Package.swift @@ -31,6 +31,7 @@ var targetDeps: [Target.Dependency] = ["WebSocketKit", "COPUS", "Sodium", "Socke let package = Package( name: "SwiftDiscord", + platforms: [.macOS(.v10_15)], products: [ .library(name: "SwiftDiscord", targets: ["SwiftDiscord"]) ], From de6594e6b75036eff7d44f11a47b7d9b913bff7a Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 29 Jun 2020 15:30:20 +0200 Subject: [PATCH 042/104] Fix a typo in EndpointRequest --- Sources/SwiftDiscord/Rest/DiscordEndpoint.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 2ab53c346..14ff5d365 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -170,7 +170,7 @@ public extension DiscordEndpoint { /// A POST request. case post(content: HTTPContent?, extraHeaders: [DiscordHeader: String]?) - /// A POST request. + /// A PUT request. case put(content: HTTPContent?, extraHeaders: [DiscordHeader: String]?) /// A PATCH request. From 3d0f88f585f2c67d8e4a14126721eb59984ebb2d Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 29 Jun 2020 15:46:03 +0200 Subject: [PATCH 043/104] Make sure that all PUT/POST/PATCH requests have a body --- .../SwiftDiscord/Rest/DiscordEndpoint.swift | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 2ab53c346..0cf2de3cf 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -221,20 +221,21 @@ public extension DiscordEndpoint { private func addContent(to request: inout URLRequest) { let content: HTTPContent? let extraHeaders: [DiscordHeader: String]? + let requiresBody: Bool switch self { case let .get(_, headers?): - (content, extraHeaders) = (nil, headers) + (content, extraHeaders, requiresBody) = (nil, headers, false) case let .post(optionalContent, headers): - (content, extraHeaders) = (optionalContent, headers) + (content, extraHeaders, requiresBody) = (optionalContent, headers, true) case let .put(optionalContent, headers): - (content, extraHeaders) = (optionalContent, headers) + (content, extraHeaders, requiresBody) = (optionalContent, headers, true) case let .patch(optionalContent, headers): - (content, extraHeaders) = (optionalContent, headers) + (content, extraHeaders, requiresBody) = (optionalContent, headers, true) case let .delete(optionalContent, headers): - (content, extraHeaders) = (optionalContent, headers) + (content, extraHeaders, requiresBody) = (optionalContent, headers, false) default: - (content, extraHeaders) = (nil, nil) + (content, extraHeaders, requiresBody) = (nil, nil, false) } for (header, value) in extraHeaders ?? [:] { @@ -243,7 +244,11 @@ public extension DiscordEndpoint { } switch content { - case nil: break + case nil: + if requiresBody { + request.httpBody = Data() + request.setValue("0", forHTTPHeaderField: "Content-Length") + } case let .json(data)?: request.httpBody = data request.setValue(HTTPContent.jsonType, forHTTPHeaderField: "Content-Type") From b6bab9839306e9cb9b44e4d6fa48533fa4ea7bd9 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 16 Jul 2020 18:40:18 +0200 Subject: [PATCH 044/104] Add support for multiple activites in a presence See https://discord.com/developers/docs/topics/gateway#presence-update --- Sources/SwiftDiscord/User/DiscordPresence.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/SwiftDiscord/User/DiscordPresence.swift b/Sources/SwiftDiscord/User/DiscordPresence.swift index b72005b70..e9db70aeb 100644 --- a/Sources/SwiftDiscord/User/DiscordPresence.swift +++ b/Sources/SwiftDiscord/User/DiscordPresence.swift @@ -30,6 +30,9 @@ public struct DiscordPresence { /// The game this user is playing, if they are playing a game. public var game: DiscordActivity? + /// All of the user's current activies. + public var activities: [DiscordActivity] + /// This user's nick on this guild. public var nick: String? @@ -43,6 +46,7 @@ public struct DiscordPresence { self.guildId = guildId user = DiscordUser(userObject: presenceObject.get("user", or: [String: Any]())) game = DiscordActivity(gameObject: presenceObject["game"] as? [String: Any]) + activities = (presenceObject["activities"] as? [[String: Any]])?.map(DiscordActivity.init(gameObject:)).compactMap { $0 } ?? [] nick = presenceObject["nick"] as? String status = DiscordPresenceStatus(rawValue: presenceObject.get("status", or: "")) ?? .offline roles = [] @@ -55,6 +59,10 @@ public struct DiscordPresence { self.game = nil } + if let activities = presenceObject["activities"] as? [[String: Any]] { + self.activities = activities.map(DiscordActivity.init(gameObject:)).compactMap { $0 } + } + if let nick = presenceObject["nick"] as? String { self.nick = nick } From edf8d06674dfee5d0a51f397ec8e5f8b2442640d Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 18 Jul 2020 19:48:48 +0200 Subject: [PATCH 045/104] Update URLs to discord.com Discord is currently migrating their non-CDN-domains from discordapp.com to discord.com and will be dropping API support for discordapp.com on November 7, 2020. --- Sources/SwiftDiscord/Audit/DiscordAuditLogEntry.swift | 2 +- Sources/SwiftDiscord/Rest/DiscordEndpoint.swift | 2 +- Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift | 2 +- Sources/SwiftDiscord/Rest/DiscordOAuth.swift | 2 +- Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift | 2 +- Tests/SwiftDiscordTests/TestDiscordDataStructures.swift | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftDiscord/Audit/DiscordAuditLogEntry.swift b/Sources/SwiftDiscord/Audit/DiscordAuditLogEntry.swift index 34e2f456b..133cfe4bb 100644 --- a/Sources/SwiftDiscord/Audit/DiscordAuditLogEntry.swift +++ b/Sources/SwiftDiscord/Audit/DiscordAuditLogEntry.swift @@ -32,7 +32,7 @@ public struct DiscordAuditLogEntry { // TODO An actual struct for this? /// Optional audit entry information for certain action types. - /// [Structure](https://discordapp.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info) + /// [Structure](https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info) public let options: [String: Any] /// The reason for this entry. diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 2ab53c346..257448ecd 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -261,7 +261,7 @@ public extension DiscordEndpoint { var description: String { switch self { case .baseURL: - return "https://discordapp.com/api/v6" + return "https://discord.com/api/v6" /* Channels */ case let .channel(id): diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift index 546f4f14a..c0fb6f818 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointGateway.swift @@ -32,7 +32,7 @@ struct DiscordEndpointGateway { #if os(Linux) return "wss://gateway.discord.gg" #else - var request = URLRequest(url: URL(string: "https://discordapp.com/api/gateway")!) + var request = URLRequest(url: URL(string: "https://discord.com/api/gateway")!) request.httpMethod = "GET" diff --git a/Sources/SwiftDiscord/Rest/DiscordOAuth.swift b/Sources/SwiftDiscord/Rest/DiscordOAuth.swift index 8ec91a51b..65e919427 100644 --- a/Sources/SwiftDiscord/Rest/DiscordOAuth.swift +++ b/Sources/SwiftDiscord/Rest/DiscordOAuth.swift @@ -20,7 +20,7 @@ import Foundation /// Represents the Discord OAuth endpoint and the different scopes Disocrd has. public enum DiscordOAuthEndpoint : String { /// The base OAuth endpoint. - case baseURL = "https://discordapp.com/api/oauth2/authorize" + case baseURL = "https://discord.com/api/oauth2/authorize" /// The bot scope. case bot diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift index 9d80486d3..fb53c04d1 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceEngine.swift @@ -287,7 +287,7 @@ public final class DiscordVoiceEngine : DiscordVoiceEngineSpec { return (ipString, port) } - // https://discordapp.com/developers/docs/topics/voice-connections#ip-discovery + // https://discord.com/developers/docs/topics/voice-connections#ip-discovery private func findIP() { udpQueueWrite.async { guard let udpSocket = self.udpSocket else { return } diff --git a/Tests/SwiftDiscordTests/TestDiscordDataStructures.swift b/Tests/SwiftDiscordTests/TestDiscordDataStructures.swift index 6f5fd6691..53d617fd6 100644 --- a/Tests/SwiftDiscordTests/TestDiscordDataStructures.swift +++ b/Tests/SwiftDiscordTests/TestDiscordDataStructures.swift @@ -68,7 +68,7 @@ public class TestDiscordDataStructures : XCTestCase { func testEmbedJSONification() { let dummyIconA = URL(string: "https://cdn.discordapp.com/embed/avatars/0.png")! let dummyIconB = URL(string: "https://cdn.discordapp.com/embed/avatars/1.png")! - let dummyURL = URL(string: "https://discordapp.com")! + let dummyURL = URL(string: "https://discord.com")! let embed1 = DiscordEmbed( title: "Title", From caf0753d7c365ff1243ce3553134d06397d6bd6c Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 3 Sep 2020 13:57:56 +0200 Subject: [PATCH 046/104] Add endpoint consumer for emojis and CreateEmoji endpoint --- .../SwiftDiscord/Rest/DiscordEndpoint.swift | 15 ++++++++++ .../Rest/DiscordEndpointConsumer+Emoji.swift | 29 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 25317849b..722e2b909 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -152,6 +152,11 @@ public enum DiscordEndpoint : CustomStringConvertible { case webhookGithub(id: WebhookID, token: String) /* End Webhooks */ + /* Emoji */ + // The guild's emojis endpoint. + case guildEmojis(guild: GuildID) + /* End Emoji */ + var combined: String { return DiscordEndpoint.baseURL.description + description } @@ -354,6 +359,11 @@ public extension DiscordEndpoint { case let .webhookGithub(id, token): return "/webhooks/\(id)/\(token)/github" /* End Webhooks */ + + /* Emoji */ + case let .guildEmojis(guild): + return "/guilds/\(guild)/emojis" + /* End Emoji */ } } @@ -457,6 +467,11 @@ public extension DiscordEndpoint { case .webhookGithub: return DiscordRateLimitKey(urlParts: [.webhooks, .webhookID, .webhookToken, .github]) /* End Webhooks */ + + /* Emoji */ + case let .guildEmojis(guild): + return DiscordRateLimitKey(id: guild, urlParts: [.guilds, .guildID, .webhooks]) + /* End Emoji */ } } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift new file mode 100644 index 000000000..6f02ef048 --- /dev/null +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift @@ -0,0 +1,29 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +import Logging + +fileprivate let logger = Logger(label: "DiscordEndpointEmoji") + +public extension DiscordEndpointConsumer where Self: DiscordUserActor { + // Default implementation + func createGuildEmoji(on guildId: GuildID, + name: String, + image: String, + roles: [RoleID], + callback: ((Bool, HTTPURLResponse?) -> ())?) { + var createJSON = [String: Encodable]() + + createJSON["name"] = name + createJSON["image"] = image + createJSON["roles"] = roles.map { String($0.rawValue) } + + guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(createJSON)) else { return } + + rateLimiter.executeRequest(endpoint: .guildEmojis(guild: guildId), + token: token, + requestInfo: .post(content: .json(contentData), extraHeaders: nil), + callback: { _, response, _ in callback?(response?.statusCode == 204, response) }) + } +} From ee3372f08ce24adc0c28c1e09f3ef0a93753822d Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 3 Sep 2020 14:04:23 +0200 Subject: [PATCH 047/104] Add DiscordEndpointConsumer.getGuildEmojis --- .../Rest/DiscordEndpointConsumer+Emoji.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift index 6f02ef048..1e990ab33 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift @@ -26,4 +26,23 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { requestInfo: .post(content: .json(contentData), extraHeaders: nil), callback: { _, response, _ in callback?(response?.statusCode == 204, response) }) } + + // Default implementation + func getGuildEmojis(on guildId: GuildID, + callback: @escaping ([DiscordEmoji], HTTPURLResponse?) -> ()) { + let requestCallback: DiscordRequestCallback = { data, response, error in + guard case let .array(rawEmojis)? = JSON.jsonFromResponse(data: data, response: response), + let emojis = rawEmojis as? [[String: Any]] else { + callback([], response) + return + } + + callback(emojis.map(DiscordEmoji.init(emojiObject:)), response) + } + + rateLimiter.executeRequest(endpoint: .guildEmojis(guild: guildId), + token: token, + requestInfo: .get(params: nil, extraHeaders: nil), + callback: requestCallback) + } } From 1794d8c87be71522bbf20f5d036dc4977afb1d78 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 3 Sep 2020 14:04:53 +0200 Subject: [PATCH 048/104] Add a default value for the callback in createGuildEmoji --- Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift index 1e990ab33..8cf2d2f4c 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift @@ -12,7 +12,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { name: String, image: String, roles: [RoleID], - callback: ((Bool, HTTPURLResponse?) -> ())?) { + callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { var createJSON = [String: Encodable]() createJSON["name"] = name From b7434ed55d1a7b150956bae481f9df8e7016c592 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 3 Sep 2020 14:09:30 +0200 Subject: [PATCH 049/104] Add guild emoji endpoint Additionally, update the rate limit keys to include new emoji URL fragments. --- Sources/SwiftDiscord/Rest/DiscordEndpoint.swift | 9 ++++++++- Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 722e2b909..5b8b1ec32 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -155,6 +155,9 @@ public enum DiscordEndpoint : CustomStringConvertible { /* Emoji */ // The guild's emojis endpoint. case guildEmojis(guild: GuildID) + + // The guild's emoji endpoint + case guildEmoji(guild: GuildID, emoji: EmojiID) /* End Emoji */ var combined: String { @@ -363,6 +366,8 @@ public extension DiscordEndpoint { /* Emoji */ case let .guildEmojis(guild): return "/guilds/\(guild)/emojis" + case let .guildEmoji(guild, emoji): + return "/guilds/\(guild)/emojis/\(emoji)" /* End Emoji */ } } @@ -470,7 +475,9 @@ public extension DiscordEndpoint { /* Emoji */ case let .guildEmojis(guild): - return DiscordRateLimitKey(id: guild, urlParts: [.guilds, .guildID, .webhooks]) + return DiscordRateLimitKey(id: guild, urlParts: [.guilds, .guildID, .emojis]) + case let .guildEmoji(guild, _): + return DiscordRateLimitKey(id: guild, urlParts: [.guilds, .guildID, .emojis, .emojiID]) /* End Emoji */ } } diff --git a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift index 448eb7f09..f593b42fc 100644 --- a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift +++ b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift @@ -254,7 +254,9 @@ public struct DiscordRateLimitKey : Hashable { static let auditLog = DiscordRateLimitURLParts(rawValue: 1 << 25) static let reactions = DiscordRateLimitURLParts(rawValue: 1 << 26) static let emoji = DiscordRateLimitURLParts(rawValue: 1 << 27) - static let me = DiscordRateLimitURLParts(rawValue: 1 << 28) + static let emojis = DiscordRateLimitURLParts(rawValue: 1 << 28) + static let emojiID = DiscordRateLimitURLParts(rawValue: 1 << 29) + static let me = DiscordRateLimitURLParts(rawValue: 1 << 30) public init(rawValue: Int) { self.rawValue = rawValue From d8814ebc9c2c35959fc49216c5edae9cced80aad Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 3 Sep 2020 14:14:39 +0200 Subject: [PATCH 050/104] Add DiscordEndpointConsumer.getGuildEmoji --- .../Rest/DiscordEndpointConsumer+Emoji.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift index 8cf2d2f4c..d350a0613 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift @@ -45,4 +45,23 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { requestInfo: .get(params: nil, extraHeaders: nil), callback: requestCallback) } + + // Default implementation + func getGuildEmoji(on guildId: GuildID, + for emojiId: EmojiID, + callback: @escaping (DiscordEmoji?, HTTPURLResponse?) -> ()) { + let requestCallback: DiscordRequestCallback = { data, response, error in + guard case let .object(emoji)? = JSON.jsonFromResponse(data: data, response: response) else { + callback(nil, response) + return + } + + callback(DiscordEmoji(emojiObject: emoji), response) + } + + rateLimiter.executeRequest(endpoint: .guildEmoji(guild: guildId, emoji: emojiId), + token: token, + requestInfo: .get(params: nil, extraHeaders: nil), + callback: requestCallback) + } } From 7afc8e90cbfbb44599080b52b671b885eff0bf62 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 3 Sep 2020 14:18:34 +0200 Subject: [PATCH 051/104] Add DiscordEndpointConsumer.deleteGuildEmoji --- .../Rest/DiscordEndpointConsumer+Emoji.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift index d350a0613..5e5eee2b9 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift @@ -64,4 +64,14 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { requestInfo: .get(params: nil, extraHeaders: nil), callback: requestCallback) } + + // Default implementation + func deleteGuildEmoji(on guildId: GuildID, + for emojiId: EmojiID, + callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { + rateLimiter.executeRequest(endpoint: .guildEmoji(guild: guildId, emoji: emojiId), + token: token, + requestInfo: .delete(content: nil, extraHeaders: nil), + callback: { _, response, _ in callback?(response?.statusCode == 204, response) }) + } } From baf298f1102ef1742bb7a9441c5f793332b0ff47 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 3 Sep 2020 14:33:01 +0200 Subject: [PATCH 052/104] Return emoji object in createGuildEmoji --- .../Rest/DiscordEndpointConsumer+Emoji.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift index 5e5eee2b9..1c6bb9dd3 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Emoji.swift @@ -12,7 +12,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { name: String, image: String, roles: [RoleID], - callback: ((Bool, HTTPURLResponse?) -> ())? = nil) { + callback: ((DiscordEmoji?, HTTPURLResponse?) -> ())? = nil) { var createJSON = [String: Encodable]() createJSON["name"] = name @@ -21,10 +21,19 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { guard let contentData = JSON.encodeJSONData(GenericEncodableDictionary(createJSON)) else { return } + let requestCallback: DiscordRequestCallback = { data, response, error in + guard case let .object(emoji)? = JSON.jsonFromResponse(data: data, response: response) else { + callback?(nil, response) + return + } + + callback?(DiscordEmoji(emojiObject: emoji), response) + } + rateLimiter.executeRequest(endpoint: .guildEmojis(guild: guildId), token: token, requestInfo: .post(content: .json(contentData), extraHeaders: nil), - callback: { _, response, _ in callback?(response?.statusCode == 204, response) }) + callback: requestCallback) } // Default implementation From 56c269e207b907a16fc94b3e79e7cd5352010449 Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 9 Nov 2020 20:20:06 +0100 Subject: [PATCH 053/104] Add reaction deletion endpoints Implement DiscordEndpointConsumer.deleteOwnReaction and .deleteUserReaction. --- .../DiscordEndpointConsumer+Channels.swift | 31 +++++++++++++++++++ .../Rest/DiscordEndpointConsumer.swift | 28 +++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift index fdc36817f..f4d709dac 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Channels.swift @@ -109,6 +109,37 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { callback: requestCallback) } + /// Default implementation + func deleteOwnReaction(for messageId: MessageID, + on channelId: ChannelID, + emoji: String, + callback: ((Bool, HTTPURLResponse?) -> ())?) { + let requestCallback: DiscordRequestCallback = { data, response, error in + callback?(response?.statusCode == 204, response) + } + + rateLimiter.executeRequest(endpoint: .reactions(channel: channelId, message: messageId, emoji: emoji.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? emoji), + token: token, + requestInfo: .delete(content: nil, extraHeaders: nil), + callback: requestCallback) + } + + /// Default implementation + func deleteUserReaction(for messageId: MessageID, + on channelId: ChannelID, + emoji: String, + by userId: UserID, + callback: ((Bool, HTTPURLResponse?) -> ())?) { + let requestCallback: DiscordRequestCallback = { data, response, error in + callback?(response?.statusCode == 204, response) + } + + rateLimiter.executeRequest(endpoint: .userReactions(channel: channelId, message: messageId, emoji: emoji.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? emoji, user: userId), + token: token, + requestInfo: .delete(content: nil, extraHeaders: nil), + callback: requestCallback) + } + /// Default implementation func deleteChannel(_ channelId: ChannelID, reason: String? = nil, diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift index 5404e7413..6afbc0d9e 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift @@ -85,6 +85,34 @@ public protocol DiscordEndpointConsumer { emoji: String, callback: ((DiscordMessage?, HTTPURLResponse?) -> ())?) + /// + /// Deletes a reaction the current user has made for the specified message. + /// + /// - parameter for: The message that is to be edited's snowflake id + /// - parameter on: The channel that we are editing on + /// - parameter emoji: The emoji name + /// - parameter callback: An optional callback containing the edited message, if successful. + /// + func deleteOwnReaction(for messageId: MessageID, + on channelId: ChannelID, + emoji: String, + callback: ((Bool, HTTPURLResponse?) -> ())?) + + /// + /// Deletes a reaction another user has made for the specified message. + /// + /// - parameter for: The message that is to be edited's snowflake id + /// - parameter on: The channel that we are editing on + /// - parameter emoji: The emoji name + /// - parameter by: The snowflake id of the user + /// - parameter callback: An optional callback containing the edited message, if successful. + /// + func deleteUserReaction(for messageId: MessageID, + on channelId: ChannelID, + emoji: String, + by userId: UserID, + callback: ((Bool, HTTPURLResponse?) -> ())?) + /// /// Deletes the specified channel. /// From e1fe1eb841907c17674ccacc72b95a6414bcc390 Mon Sep 17 00:00:00 2001 From: Tobias Date: Mon, 16 Nov 2020 17:39:08 +0100 Subject: [PATCH 054/104] Added banner --- Sources/SwiftDiscord/Guild/DiscordGuild.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/SwiftDiscord/Guild/DiscordGuild.swift b/Sources/SwiftDiscord/Guild/DiscordGuild.swift index bb00ddc44..e2c2446f6 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuild.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuild.swift @@ -91,6 +91,9 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { /// The base64 encoded icon image for this guild. public private(set) var icon: String + /// The base64 encoded banner image for this guild. + public private(set) var banner: String + /// The multi-factor authentication level for this guild. public private(set) var mfaLevel: Int @@ -115,6 +118,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { emojis = DiscordEmoji.emojisFromArray(guildObject.get("emojis", or: JSONArray())) features = guildObject.get("features", or: Array()) icon = guildObject.get("icon", or: "") + banner = guildObject.get("banner", or: "") large = guildObject.get("large", or: false) memberCount = guildObject.get("member_count", or: 0) mfaLevel = guildObject.get("mfa_level", or: -1) @@ -321,6 +325,10 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { self.icon = icon } + if let banner = newGuild["banner"] as? String { + self.banner = banner + } + if let memberCount = newGuild["member_count"] as? Int { self.memberCount = memberCount } From 44ebb8ccc5a7514fe06a53c1aa2ff5fd32ff27be Mon Sep 17 00:00:00 2001 From: Tobias Date: Mon, 16 Nov 2020 18:11:57 +0100 Subject: [PATCH 055/104] Added NSFW channel flag --- Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift b/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift index 719616855..054357d3f 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuildChannel.swift @@ -209,6 +209,9 @@ public struct DiscordGuildTextChannel : DiscordTextChannel, DiscordGuildChannel /// The topic of this channel, if this is a text channel. public var topic: String + /// If this channel is NSFW + public var nsfw: Bool + init(guildChannelObject: [String: Any], guildID: GuildID?, client: DiscordClient? = nil) { id = Snowflake(guildChannelObject["id"] as? String) ?? 0 guildId = guildID ?? Snowflake(guildChannelObject["guild_id"] as? String) ?? 0 @@ -219,6 +222,7 @@ public struct DiscordGuildTextChannel : DiscordTextChannel, DiscordGuildChannel position = guildChannelObject.get("position", or: 0) topic = guildChannelObject.get("topic", or: "") parentId = Snowflake(guildChannelObject.get("parent_id", or: "")) + nsfw = guildChannelObject.get("nsfw", or: false) self.client = client } } From 10f130fbf6e6b6deee8fa08b89478b641237459d Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 16:46:51 +0100 Subject: [PATCH 056/104] Switch to v8 endpoints --- Sources/SwiftDiscord/Rest/DiscordEndpoint.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 5b8b1ec32..89e4e81d0 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -274,7 +274,7 @@ public extension DiscordEndpoint { var description: String { switch self { case .baseURL: - return "https://discord.com/api/v6" + return "https://discord.com/api/v8" /* Channels */ case let .channel(id): From 6c3da1954c914d3602d43fb236711ec2edddaf9f Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 16:49:05 +0100 Subject: [PATCH 057/104] Update to v8 gateway --- Sources/SwiftDiscord/Gateway/DiscordEngine.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift index a4a91c8cf..4a3da4aa6 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift @@ -39,7 +39,7 @@ open class DiscordEngine : DiscordEngineSpec { /// The url for the gateway. open var connectURL: String { - return DiscordEndpointGateway.gatewayURL + "/?v=6&encoding=json" + return DiscordEndpointGateway.gatewayURL + "/?v=8&encoding=json" } /// The type of DiscordEngineSpec. Used to correctly fire events. From 5d795dfe777d0f5b0d85f00c18ca86da1035fa5b Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:18:01 +0100 Subject: [PATCH 058/104] Add DiscordGatewayIntent --- .../Gateway/DiscordGatewayIntent.swift | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Sources/SwiftDiscord/Gateway/DiscordGatewayIntent.swift diff --git a/Sources/SwiftDiscord/Gateway/DiscordGatewayIntent.swift b/Sources/SwiftDiscord/Gateway/DiscordGatewayIntent.swift new file mode 100644 index 000000000..840be3999 --- /dev/null +++ b/Sources/SwiftDiscord/Gateway/DiscordGatewayIntent.swift @@ -0,0 +1,42 @@ +/// An intent defines the events the gateway should +/// subscribe to. +/// +/// See https://discord.com/developers/docs/topics/gateway#gateway-intents +public struct DiscordGatewayIntent : OptionSet { + public let rawValue: Int + + /// Creation, updates and deletions of guilds, roles and channels. + public static let guilds = DiscordGatewayIntent(rawValue: 1 << 0) + /// Guild member update events. This is a privileged intent. + public static let guildMembers = DiscordGatewayIntent(rawValue: 1 << 1) + /// Guild ban and unban events. + public static let guildBans = DiscordGatewayIntent(rawValue: 1 << 2) + /// Guild emoji update events. + public static let guildEmojis = DiscordGatewayIntent(rawValue: 1 << 3) + /// Guild integration update events. + public static let guildIntegrations = DiscordGatewayIntent(rawValue: 1 << 4) + /// Guild webhook update events. + public static let guildWebhooks = DiscordGatewayIntent(rawValue: 1 << 5) + /// Guild invite events. + public static let guildInvites = DiscordGatewayIntent(rawValue: 1 << 6) + /// Guild voice state update events. + public static let guildVoiceStates = DiscordGatewayIntent(rawValue: 1 << 7) + /// Guild presence update events. This is a privileged intent. + public static let guildPresences = DiscordGatewayIntent(rawValue: 1 << 8) + /// Guild message creations, updates and deletions. + public static let guildMessages = DiscordGatewayIntent(rawValue: 1 << 9) + /// Guild message reaction creations, updates and deletions. + public static let guildMessageReactions = DiscordGatewayIntent(rawValue: 1 << 10) + /// Guild typing indicators. + public static let guildMessageTyping = DiscordGatewayIntent(rawValue: 1 << 11) + /// Direct message creations, updates and deletions. + public static let directMessages = DiscordGatewayIntent(rawValue: 1 << 12) + /// Direct message reaction creations, updates and deletions. + public static let directMessageReactions = DiscordGatewayIntent(rawValue: 1 << 13) + /// Direct message typing indicators. + public static let directMessageTyping = DiscordGatewayIntent(rawValue: 1 << 14) + + public init(rawValue: Int) { + self.rawValue = rawValue + } +} From 82188ebfb917c0afda97b9ab2c3f35f3dd2f6386 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:22:42 +0100 Subject: [PATCH 059/104] Add common sets of intents --- .../Gateway/DiscordGatewayIntent.swift | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Sources/SwiftDiscord/Gateway/DiscordGatewayIntent.swift b/Sources/SwiftDiscord/Gateway/DiscordGatewayIntent.swift index 840be3999..3392b67eb 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordGatewayIntent.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordGatewayIntent.swift @@ -36,6 +36,35 @@ public struct DiscordGatewayIntent : OptionSet { /// Direct message typing indicators. public static let directMessageTyping = DiscordGatewayIntent(rawValue: 1 << 14) + /// The privileged intents (which may require enabling in the Discord developer console). + public static let privilegedIntents: DiscordGatewayIntent = [ + .guildMembers, + .guildPresences + ] + + /// The unprivileged intents. Use these if you don't need the privileged intents. + public static let unprivilegedIntents: DiscordGatewayIntent = [ + .guilds, + .guildBans, + .guildEmojis, + .guildIntegrations, + .guildWebhooks, + .guildInvites, + .guildVoiceStates, + .guildMessages, + .guildMessageReactions, + .guildMessageTyping, + .directMessages, + .directMessageReactions, + .directMessageTyping + ] + + /// All intents. + public static let allIntents: DiscordGatewayIntent = [ + .privilegedIntents, + .unprivilegedIntents + ] + public init(rawValue: Int) { self.rawValue = rawValue } From 3de9116cea0d003443a49a32886cdc4193a994f0 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:25:34 +0100 Subject: [PATCH 060/104] Pass unprivileged intents to engine by default --- Sources/SwiftDiscord/Gateway/DiscordEngine.swift | 7 ++++++- Sources/SwiftDiscord/Gateway/DiscordSharding.swift | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift index 4a3da4aa6..bd1b68f71 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift @@ -52,6 +52,7 @@ open class DiscordEngine : DiscordEngineSpec { open var handshakeObject: [String: Any] { var identify: [String: Any] = [ "token": delegate!.token.token, + "intents": intents.rawValue, "properties": [ "$os": os, "$browser": "SwiftDiscord", @@ -92,6 +93,9 @@ open class DiscordEngine : DiscordEngineSpec { /// The shard number of this engine. public let shardNum: Int + /// The intents used when connecting to the gateway. + public let intents: DiscordGatewayIntent + /// The queue that WebSockets use to parse things. public let parseQueue = DispatchQueue(label: "discordEngine.parseQueue") @@ -136,10 +140,11 @@ open class DiscordEngine : DiscordEngineSpec { /// /// - parameter delegate: The DiscordClientSpec this engine should be associated with. /// - public required init(delegate: DiscordShardDelegate, shardNum: Int = 0, numShards: Int = 1, onLoop: EventLoop) { + public required init(delegate: DiscordShardDelegate, shardNum: Int = 0, numShards: Int = 1, intents: DiscordGatewayIntent = .unprivilegedIntents, onLoop: EventLoop) { self.delegate = delegate self.shardNum = shardNum self.numShards = numShards + self.intents = intents self.runloop = onLoop } diff --git a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift index 855b20e0f..3fd9e34e1 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift @@ -91,7 +91,7 @@ public protocol DiscordShard : DiscordWebSocketable, DiscordGatewayable, Discord /// /// - parameter client: The client this engine should be associated with. /// - init(delegate: DiscordShardDelegate, shardNum: Int, numShards: Int, onLoop: EventLoop) + init(delegate: DiscordShardDelegate, shardNum: Int, numShards: Int, intents: DiscordGatewayIntent, onLoop: EventLoop) } /// Declares that a type will be a shard's delegate. From 2b635f1792f9367ae0e59a7d074faad0176db467 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:33:12 +0100 Subject: [PATCH 061/104] Add client option for specifying intents --- Sources/SwiftDiscord/DiscordClient.swift | 7 ++++++- Sources/SwiftDiscord/DiscordClientOption.swift | 5 +++++ Sources/SwiftDiscord/Gateway/DiscordEngine.swift | 2 +- Sources/SwiftDiscord/Gateway/DiscordSharding.swift | 7 ++++--- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftDiscord/DiscordClient.swift b/Sources/SwiftDiscord/DiscordClient.swift index 035345dd8..a2d28c03c 100644 --- a/Sources/SwiftDiscord/DiscordClient.swift +++ b/Sources/SwiftDiscord/DiscordClient.swift @@ -69,6 +69,9 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// If we should only represent a single shard, this is the shard information. public var shardingInfo = try! DiscordShardInformation(shardRange: 0..<1, totalShards: 1) + /// The gateway intents. + public var intents = DiscordGatewayIntent.unprivilegedIntents + /// Whether large guilds should have their users fetched as soon as they are created. public var fillLargeGuilds = false @@ -124,6 +127,8 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco self.shardingInfo = shardingInfo case let .voiceConfiguration(config): self.voiceManager.engineConfiguration = config + case let .intents(intents): + self.intents = intents case .discardPresences: discardPresences = true case .fillLargeGuilds: @@ -151,7 +156,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco open func connect() { logger.info("Connecting") - shardManager.manuallyShatter(withInfo: shardingInfo) + shardManager.manuallyShatter(withInfo: shardingInfo, intents: intents) shardManager.connect() } diff --git a/Sources/SwiftDiscord/DiscordClientOption.swift b/Sources/SwiftDiscord/DiscordClientOption.swift index c986dc788..1ba3c2349 100644 --- a/Sources/SwiftDiscord/DiscordClientOption.swift +++ b/Sources/SwiftDiscord/DiscordClientOption.swift @@ -39,6 +39,10 @@ public enum DiscordClientOption : CustomStringConvertible, Equatable { /// However this means that invsible users will also be pruned. case pruneUsers + /// The gateway intents. By default, only the unprivileged intents are used, i.e. you won't + /// get guild member and presence events, unless you specify these here (e.g. by using .allIntents). + case intents(DiscordGatewayIntent) + /// A DiscordRateLimiter for this client. All REST calls will be put through this limiter. case rateLimiter(DiscordRateLimiterSpec) @@ -60,6 +64,7 @@ public enum DiscordClientOption : CustomStringConvertible, Equatable { case .rateLimiter: return "rateLimiter" case .shardingInfo: return "shardingInfo" case .pruneUsers: return "pruneUsers" + case .intents: return "intents" case .voiceConfiguration: return "voiceConfiguration" } } diff --git a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift index bd1b68f71..c311b6176 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEngine.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEngine.swift @@ -140,7 +140,7 @@ open class DiscordEngine : DiscordEngineSpec { /// /// - parameter delegate: The DiscordClientSpec this engine should be associated with. /// - public required init(delegate: DiscordShardDelegate, shardNum: Int = 0, numShards: Int = 1, intents: DiscordGatewayIntent = .unprivilegedIntents, onLoop: EventLoop) { + public required init(delegate: DiscordShardDelegate, shardNum: Int = 0, numShards: Int = 1, intents: DiscordGatewayIntent, onLoop: EventLoop) { self.delegate = delegate self.shardNum = shardNum self.numShards = numShards diff --git a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift index 3fd9e34e1..5a0f51905 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordSharding.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordSharding.swift @@ -231,8 +231,8 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { /// - returns: A new `DiscordShard` /// open func createShardWithDelegate(_ delegate: DiscordShardManagerDelegate, withShardNum shardNum: Int, - totalShards: Int, onloop: EventLoop) -> DiscordShard { - return DiscordEngine(delegate: self, shardNum: shardNum, numShards: totalShards, onLoop: onloop) + totalShards: Int, intents: DiscordGatewayIntent, onloop: EventLoop) -> DiscordShard { + return DiscordEngine(delegate: self, shardNum: shardNum, numShards: totalShards, intents: intents, onLoop: onloop) } /// @@ -258,7 +258,7 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { /// /// - parameter withInfo: The information about this single shard. /// - open func manuallyShatter(withInfo info: DiscordShardInformation) { + open func manuallyShatter(withInfo info: DiscordShardInformation, intents: DiscordGatewayIntent) { guard let delegate = self.delegate else { return } logger.debug("(verbose) Handling shard range \(info.shardRange)") @@ -270,6 +270,7 @@ open class DiscordShardManager : DiscordShardDelegate, Lockable { shards.append(createShardWithDelegate(delegate, withShardNum: shardNum, totalShards: info.totalShards, + intents: intents, onloop: delegate.runloops.next())) } } From 97c47ef07843e3ba4c53a5299743da51a9a8c882 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:36:30 +0100 Subject: [PATCH 062/104] Remove game field from presence --- Sources/SwiftDiscord/User/DiscordPresence.swift | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Sources/SwiftDiscord/User/DiscordPresence.swift b/Sources/SwiftDiscord/User/DiscordPresence.swift index e9db70aeb..12aa7fdbc 100644 --- a/Sources/SwiftDiscord/User/DiscordPresence.swift +++ b/Sources/SwiftDiscord/User/DiscordPresence.swift @@ -27,9 +27,6 @@ public struct DiscordPresence { /// The user associated with this presence. public let user: DiscordUser - /// The game this user is playing, if they are playing a game. - public var game: DiscordActivity? - /// All of the user's current activies. public var activities: [DiscordActivity] @@ -45,7 +42,6 @@ public struct DiscordPresence { init(presenceObject: [String: Any], guildId: GuildID) { self.guildId = guildId user = DiscordUser(userObject: presenceObject.get("user", or: [String: Any]())) - game = DiscordActivity(gameObject: presenceObject["game"] as? [String: Any]) activities = (presenceObject["activities"] as? [[String: Any]])?.map(DiscordActivity.init(gameObject:)).compactMap { $0 } ?? [] nick = presenceObject["nick"] as? String status = DiscordPresenceStatus(rawValue: presenceObject.get("status", or: "")) ?? .offline @@ -53,12 +49,6 @@ public struct DiscordPresence { } mutating func updatePresence(presenceObject: [String: Any]) { - if let game = presenceObject["game"] as? [String: Any] { - self.game = DiscordActivity(gameObject: game) - } else { - self.game = nil - } - if let activities = presenceObject["activities"] as? [[String: Any]] { self.activities = activities.map(DiscordActivity.init(gameObject:)).compactMap { $0 } } From 930567b8d7d2e4496b688f5e6080746beb68b638 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:37:31 +0100 Subject: [PATCH 063/104] Migrate 'event_enabled' to 'widget_enabled' --- Sources/SwiftDiscord/Guild/DiscordGuild.swift | 8 ++++---- Tests/SwiftDiscordTests/Fixtures.swift | 2 +- Tests/SwiftDiscordTests/TestDiscordGuild.swift | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftDiscord/Guild/DiscordGuild.swift b/Sources/SwiftDiscord/Guild/DiscordGuild.swift index e2c2446f6..68b2ee3c1 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuild.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuild.swift @@ -86,7 +86,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { public private(set) var embedChannelId: ChannelID /// Whether this guild has embed enabled. - public private(set) var embedEnabled: Bool + public private(set) var widgetEnabled: Bool /// The base64 encoded icon image for this guild. public private(set) var icon: String @@ -113,7 +113,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { id = guildObject.getSnowflake() channels = guildChannels(fromArray: guildObject.get("channels", or: JSONArray()), guildID: id, client: client) defaultMessageNotifications = guildObject.get("default_message_notifications", or: -1) - embedEnabled = guildObject.get("embed_enabled", or: false) + widgetEnabled = guildObject.get("widget_enabled", or: false) embedChannelId = guildObject.getSnowflake(key: "embed_channel_id") emojis = DiscordEmoji.emojisFromArray(guildObject.get("emojis", or: JSONArray())) features = guildObject.get("features", or: Array()) @@ -317,8 +317,8 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { self.embedChannelId = embedChannelId } - if let embedEnabled = newGuild["embed_enabled"] as? Bool { - self.embedEnabled = embedEnabled + if let widgetEnabled = newGuild["widget_enabled"] as? Bool { + self.widgetEnabled = widgetEnabled } if let icon = newGuild["icon"] as? String { diff --git a/Tests/SwiftDiscordTests/Fixtures.swift b/Tests/SwiftDiscordTests/Fixtures.swift index f89f83607..865a19af7 100644 --- a/Tests/SwiftDiscordTests/Fixtures.swift +++ b/Tests/SwiftDiscordTests/Fixtures.swift @@ -95,7 +95,7 @@ let testGuildChannelCategory: [String: Any] = [ let testGuild: [String: Any] = [ "channels": [[String: Any]](), "default_message_notifications": 0, - "embed_enabled": false, + "widget_enabled": false, "embed_channel_id": "", "emojis": [[String: Any]](), "features": [Any](), diff --git a/Tests/SwiftDiscordTests/TestDiscordGuild.swift b/Tests/SwiftDiscordTests/TestDiscordGuild.swift index 970eb3793..df1e90341 100644 --- a/Tests/SwiftDiscordTests/TestDiscordGuild.swift +++ b/Tests/SwiftDiscordTests/TestDiscordGuild.swift @@ -26,7 +26,7 @@ public class TestDiscordGuild : XCTestCase { } func testCreatingGuildSetsEmbedEnabled() { - tGuild["embed_enabled"] = true + tGuild["widget_enabled"] = true let guild = DiscordGuild(guildObject: tGuild, client: nil) From d0b7c721c7806602f5fdbb867fc1d72210dd151b Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:39:34 +0100 Subject: [PATCH 064/104] Migrate 'embed_channel_id' to 'widget_channel_id' --- Sources/SwiftDiscord/Guild/DiscordGuild.swift | 8 ++++---- Tests/SwiftDiscordTests/Fixtures.swift | 2 +- Tests/SwiftDiscordTests/TestDiscordGuild.swift | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftDiscord/Guild/DiscordGuild.swift b/Sources/SwiftDiscord/Guild/DiscordGuild.swift index 68b2ee3c1..d952751a0 100644 --- a/Sources/SwiftDiscord/Guild/DiscordGuild.swift +++ b/Sources/SwiftDiscord/Guild/DiscordGuild.swift @@ -83,7 +83,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { public private(set) var defaultMessageNotifications: Int /// The snowflake id of the embed channel for this guild. - public private(set) var embedChannelId: ChannelID + public private(set) var widgetChannelId: ChannelID /// Whether this guild has embed enabled. public private(set) var widgetEnabled: Bool @@ -114,7 +114,7 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { channels = guildChannels(fromArray: guildObject.get("channels", or: JSONArray()), guildID: id, client: client) defaultMessageNotifications = guildObject.get("default_message_notifications", or: -1) widgetEnabled = guildObject.get("widget_enabled", or: false) - embedChannelId = guildObject.getSnowflake(key: "embed_channel_id") + widgetChannelId = guildObject.getSnowflake(key: "widget_channel_id") emojis = DiscordEmoji.emojisFromArray(guildObject.get("emojis", or: JSONArray())) features = guildObject.get("features", or: Array()) icon = guildObject.get("icon", or: "") @@ -313,8 +313,8 @@ public final class DiscordGuild : DiscordClientHolder, CustomStringConvertible { self.defaultMessageNotifications = defaultMessageNotifications } - if let embedChannelId = Snowflake(newGuild["embed_channel_id"] as? String) { - self.embedChannelId = embedChannelId + if let widgetChannelId = Snowflake(newGuild["widget_channel_id"] as? String) { + self.widgetChannelId = widgetChannelId } if let widgetEnabled = newGuild["widget_enabled"] as? Bool { diff --git a/Tests/SwiftDiscordTests/Fixtures.swift b/Tests/SwiftDiscordTests/Fixtures.swift index 865a19af7..b86aeb554 100644 --- a/Tests/SwiftDiscordTests/Fixtures.swift +++ b/Tests/SwiftDiscordTests/Fixtures.swift @@ -96,7 +96,7 @@ let testGuild: [String: Any] = [ "channels": [[String: Any]](), "default_message_notifications": 0, "widget_enabled": false, - "embed_channel_id": "", + "widget_channel_id": "", "emojis": [[String: Any]](), "features": [Any](), "icon": "", diff --git a/Tests/SwiftDiscordTests/TestDiscordGuild.swift b/Tests/SwiftDiscordTests/TestDiscordGuild.swift index df1e90341..2ce0a434c 100644 --- a/Tests/SwiftDiscordTests/TestDiscordGuild.swift +++ b/Tests/SwiftDiscordTests/TestDiscordGuild.swift @@ -25,20 +25,20 @@ public class TestDiscordGuild : XCTestCase { XCTAssertEqual(guild.defaultMessageNotifications, 0, "init should set default message notifications") } - func testCreatingGuildSetsEmbedEnabled() { + func testCreatingGuildSetsWidgetEnabled() { tGuild["widget_enabled"] = true let guild = DiscordGuild(guildObject: tGuild, client: nil) - XCTAssertTrue(guild.embedEnabled, "init should set embed enabled") + XCTAssertTrue(guild.widgetEnabled, "init should set widget enabled") } - func testCreatingGuildSetsEmbedChannel() { - tGuild["embed_channel_id"] = "200" + func testCreatingGuildSetsWidgetChannel() { + tGuild["widget_channel_id"] = "200" let guild = DiscordGuild(guildObject: tGuild, client: nil) - XCTAssertEqual(guild.embedChannelId, 200, "init should set the embed channel id") + XCTAssertEqual(guild.widgetChannelId, 200, "init should set the widget channel id") } func testCreatingGuildSetsIcon() { @@ -229,8 +229,8 @@ public class TestDiscordGuild : XCTestCase { ("testCreatingGuildSetsId", testCreatingGuildSetsId), ("testCreatingGuildSetsName", testCreatingGuildSetsName), ("testCreatingGuildSetsDefaultMessageNotifications", testCreatingGuildSetsDefaultMessageNotifications), - ("testCreatingGuildSetsEmbedEnabled", testCreatingGuildSetsEmbedEnabled), - ("testCreatingGuildSetsEmbedChannel", testCreatingGuildSetsEmbedChannel), + ("testCreatingGuildSetsWidgetEnabled", testCreatingGuildSetsWidgetEnabled), + ("testCreatingGuildSetsWidgetChannel", testCreatingGuildSetsWidgetChannel), ("testCreatingGuildSetsIcon", testCreatingGuildSetsIcon), ("testCreatingGuildSetsLarge", testCreatingGuildSetsLarge), ("testCreatingGuildSetsMemberCount", testCreatingGuildSetsMemberCount), From 65f7e0cea50f37360caa76490acff4ecc6d4eb5f Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:40:05 +0100 Subject: [PATCH 065/104] Set intents in test engine --- Tests/SwiftDiscordTests/TestDiscordEngine.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SwiftDiscordTests/TestDiscordEngine.swift b/Tests/SwiftDiscordTests/TestDiscordEngine.swift index d97e3c6d9..6bd13b5c7 100644 --- a/Tests/SwiftDiscordTests/TestDiscordEngine.swift +++ b/Tests/SwiftDiscordTests/TestDiscordEngine.swift @@ -52,7 +52,7 @@ public class TestDiscordEngine : XCTestCase, DiscordShardDelegate { public override func setUp() { loop = MultiThreadedEventLoopGroup(numberOfThreads: 1) - engine = DiscordEngine(delegate: self, onLoop: loop.next()) + engine = DiscordEngine(delegate: self, intents: .unprivilegedIntents, onLoop: loop.next()) } } From 2c330ea7c83b3d02c37f8a49fd587a565bb0de61 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:44:05 +0100 Subject: [PATCH 066/104] Use floating points for rate limits --- .../Rest/DiscordRateLimiter.swift | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift index f593b42fc..05bd54289 100644 --- a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift +++ b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift @@ -70,7 +70,7 @@ public final class DiscordRateLimiter : DiscordRateLimiterSpec { if endpointLimits[endpointKey] == nil { // First time handling this endpoint, err on the side caution and limit to one endpointLimits[endpointKey] = DiscordRateLimit(endpointKey: endpointKey, limit: 1, remaining: 1, - reset: Int(Date().timeIntervalSince1970) + 3) + reset: Date().timeIntervalSince1970 + 3) } let rateLimit = endpointLimits[endpointKey]! @@ -155,9 +155,9 @@ public final class DiscordRateLimiter : DiscordRateLimiterSpec { let remaining = response.allHeaderFields["x-ratelimit-remaining"], let reset = response.allHeaderFields["x-ratelimit-reset"] { // Update the limit and attempt to schedule a limit reset - rateLimit.updateLimits(limit: Int(limit as! String)!, - remaining: Int(remaining as! String)!, - reset: Int(reset as! String)!) + rateLimit.updateLimits(limit: Double(limit as! String)!, + remaining: Double(remaining as! String)!, + reset: Double(reset as! String)!) } logger.debug("New limit: \(rateLimit.limit)") @@ -298,9 +298,9 @@ public struct DiscordRateLimitKey : Hashable { /// Enqueued requests are handled through limit resets. Which are told to us by Discord in the x-ratelimit-reset header. /// It's up to the DiscordRateLimiter to actually call the scheduleReset method. private final class DiscordRateLimit { - var limit: Int - var remaining: Int - var reset: Int + var limit: Double + var remaining: Double + var reset: Double var queue = [RateLimitedRequest]() private let endpointKey: DiscordRateLimitKey @@ -312,14 +312,14 @@ private final class DiscordRateLimit { } private var deadlineForReset: DispatchTime { - let seconds = reset - Int(Date().timeIntervalSince1970) + let seconds = reset - Date().timeIntervalSince1970 guard seconds > 0 else { return DispatchTime(uptimeNanoseconds: 0) } return DispatchTime.now() + Double(seconds) } - init(endpointKey: DiscordRateLimitKey, limit: Int, remaining: Int, reset: Int) { + init(endpointKey: DiscordRateLimitKey, limit: Double, remaining: Double, reset: Double) { self.endpointKey = endpointKey self.limit = limit self.remaining = remaining @@ -346,13 +346,13 @@ private final class DiscordRateLimit { limiter.executeRequest(limitedRequest.request, for: self.endpointKey, callback: limitedRequest.callback) removed += 1 - } while removed < self.remaining && self.queue.count != 0 + } while removed < Int(self.remaining) && self.queue.count != 0 logger.debug("Sent \(removed) requests for limit: \(self.endpointKey)") } } - func updateLimits(limit: Int, remaining: Int, reset: Int) { + func updateLimits(limit: Double, remaining: Double, reset: Double) { self.limit = limit self.remaining = remaining self.reset = reset From aea37f8e94880d8ba9f39acd22a2ec67e8fc4f39 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 17:55:05 +0100 Subject: [PATCH 067/104] Add DiscordAllowedMentions structure For finer-grained control over the mentions in an outgoing message. --- .../SwiftDiscord/Channel/DiscordMessage.swift | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index 10d556638..24256d2e7 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -24,6 +24,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { let content: String let tts: Bool let embed: DiscordEmbed? + let allowedMentions: DiscordAllowedMentions? } // MARK: Typealiases @@ -93,6 +94,9 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { /// Whether or not this message should be read by a screen reader. public let tts: Bool + /// Finer-grained control over the allowed mentions in an outgoing message. + public let allowedMentions: DiscordAllowedMentions? + /// The type of this message. public let type: MessageType @@ -131,6 +135,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { tts = messageObject.get("tts", or: false) editedTimestamp = DiscordDateFormatter.format(messageObject.get("edited_timestamp", or: "")) ?? Date() timestamp = DiscordDateFormatter.format(messageObject.get("timestamp", or: "")) ?? Date() + allowedMentions = nil files = [] type = MessageType(rawValue: messageObject.get("type", or: 0)) ?? .default self.client = client @@ -144,7 +149,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { /// - parameter files: The files to send with this message. /// - parameter tts: Whether this message should be text-to-speach. /// - public init(content: String, embed: DiscordEmbed? = nil, files: [DiscordFileUpload] = [], tts: Bool = false) { + public init(content: String, embed: DiscordEmbed? = nil, files: [DiscordFileUpload] = [], tts: Bool = false, allowedMentions: DiscordAllowedMentions? = nil) { self.content = content if let embed = embed { self.embeds = [embed] @@ -155,6 +160,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { self.application = nil self.files = files self.tts = tts + self.allowedMentions = allowedMentions self.attachments = [] self.author = DiscordUser(userObject: [:]) self.channelId = 0 @@ -200,7 +206,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { // MARK: Methods func createDataForSending() -> Either { - let fields = FieldsList(content: content, tts: tts, embed: embeds.first) + let fields = FieldsList(content: content, tts: tts, embed: embeds.first, allowedMentions: allowedMentions) let fieldsData = JSON.encodeJSONData(fields) ?? Data() if files.count > 0 { return .right(createMultipartBody(encodedJSON: fieldsData, files: files)) @@ -799,3 +805,29 @@ public struct DiscordReaction { return reactionsArray.map(DiscordReaction.init) } } + +public enum DiscordAllowedMentionType : String, Encodable { + case roles + case users + case everyone +} + +/// Allows for more granular control over mentions +/// without having to modify the message content. +public struct DiscordAllowedMentions : Encodable { + public enum CodingKeys : String, CodingKey { + case parse + case roles + case users + case repliedUser = "replied_user" + } + + /// An array of allowed mentions types to parse from the content. + public let parse: DiscordAllowedMentionType + /// Array of role ids to mention. + public let roles: [RoleID] + /// Array of user ids to mention. + public let users: [UserID] + /// For replies, whether to mention the author of the message being replied to (default: false) + public let repliedUser: Bool +} From acf84e38310109f85ba855c9c68c4d0bdf67ae11 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 18:10:12 +0100 Subject: [PATCH 068/104] Add reply to message types --- .../SwiftDiscord/Channel/DiscordMessage.swift | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index 24256d2e7..cfa3ddf49 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -231,28 +231,52 @@ public extension DiscordMessage { /// Type of message enum MessageType : Int { /// Default. - case `default` + case `default` = 0 /// Recipient Add. - case recipientAdd + case recipientAdd = 1 /// Recipient Remove. - case recipientRemove + case recipientRemove = 2 /// Call. - case call + case call = 3 /// Channel name change. - case channelNameChange + case channelNameChange = 4 /// Channel icon change. - case channelIconChange + case channelIconChange = 5 /// Channel pinned message. - case channelPinnedMessage + case channelPinnedMessage = 6 /// Guild member join. - case guildMemberJoin + case guildMemberJoin = 7 + + /// User premium guild subscription. + case userPremiumGuildSubscription = 8 + + /// User premium guild subscription tier 1. + case userPremiumGuildSubscriptionTier1 = 9 + + /// User premium guild subscription tier 2. + case userPremiumGuildSubscriptionTier2 = 10 + + /// User premium guild subscription tier 3. + case userPremiumGuildSubscriptionTier3 = 11 + + /// Channel follow add. + case channelFollowAdd = 12 + + /// Guild discovery disqualified. + case guildDiscoveryDisqualified = 14 + + /// Guild discovery requalified. + case guildDiscoveryRequalified = 15 + + /// Message reply. + case reply = 19 } /// Represents an action that be taken on a message. From 40b3de968b3bf502f62eec900074b8e18d7a8fd8 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 18:22:28 +0100 Subject: [PATCH 069/104] Add support for referenced messages and replies --- .../SwiftDiscord/Channel/DiscordMessage.swift | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index cfa3ddf49..8dbad3bac 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -21,10 +21,19 @@ import Foundation public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { // Used for `createDataForSending` private struct FieldsList : Encodable { + enum CodingKeys: String, CodingKey { + case content + case tts + case embed + case allowedMentions = "allowed_mentions" + case messageReference = "message_reference" + } + let content: String let tts: Bool let embed: DiscordEmbed? let allowedMentions: DiscordAllowedMentions? + let messageReference: DiscordMessageReference? } // MARK: Typealiases @@ -97,6 +106,16 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { /// Finer-grained control over the allowed mentions in an outgoing message. public let allowedMentions: DiscordAllowedMentions? + /// A referenced message in an incoming message. Only present if it's a reply. + /// + /// TODO: This is actually a DiscordMessage object too, but would cause the + /// value type to become recursive, which is not allowed yet (since optionals + /// are value types themselves that do not box the value). + public let referencedMessage: [String: Any]? + + /// A referenced message in an outgoing message. + public let messageReference: DiscordMessageReference? + /// The type of this message. public let type: MessageType @@ -136,6 +155,8 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { editedTimestamp = DiscordDateFormatter.format(messageObject.get("edited_timestamp", or: "")) ?? Date() timestamp = DiscordDateFormatter.format(messageObject.get("timestamp", or: "")) ?? Date() allowedMentions = nil + referencedMessage = messageObject.get("referenced_message", as: [String: Any].self) + messageReference = nil files = [] type = MessageType(rawValue: messageObject.get("type", or: 0)) ?? .default self.client = client @@ -149,7 +170,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { /// - parameter files: The files to send with this message. /// - parameter tts: Whether this message should be text-to-speach. /// - public init(content: String, embed: DiscordEmbed? = nil, files: [DiscordFileUpload] = [], tts: Bool = false, allowedMentions: DiscordAllowedMentions? = nil) { + public init(content: String, embed: DiscordEmbed? = nil, files: [DiscordFileUpload] = [], tts: Bool = false, allowedMentions: DiscordAllowedMentions? = nil, messageReference: DiscordMessageReference? = nil) { self.content = content if let embed = embed { self.embeds = [embed] @@ -161,6 +182,8 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { self.files = files self.tts = tts self.allowedMentions = allowedMentions + self.messageReference = messageReference + self.referencedMessage = nil self.attachments = [] self.author = DiscordUser(userObject: [:]) self.channelId = 0 @@ -206,7 +229,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { // MARK: Methods func createDataForSending() -> Either { - let fields = FieldsList(content: content, tts: tts, embed: embeds.first, allowedMentions: allowedMentions) + let fields = FieldsList(content: content, tts: tts, embed: embeds.first, allowedMentions: allowedMentions, messageReference: messageReference) let fieldsData = JSON.encodeJSONData(fields) ?? Data() if files.count > 0 { return .right(createMultipartBody(encodedJSON: fieldsData, files: files)) @@ -855,3 +878,16 @@ public struct DiscordAllowedMentions : Encodable { /// For replies, whether to mention the author of the message being replied to (default: false) public let repliedUser: Bool } + +/// A reference to a message, e.g. used in outgoing replies. +public struct DiscordMessageReference : Encodable { + public enum CodingKeys : String, CodingKey { + case messageId = "message_id" + case channelId = "channel_id" + case guildId = "guild_id" + } + + public let messageId: MessageID? + public let channelId: ChannelID? + public let guildId: GuildID? +} From 9c4cb656fa0c693b37f8959fee03b59e1e5904b0 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 18:25:22 +0100 Subject: [PATCH 070/104] Add public initializers to allowed mention and message ref structs --- Sources/SwiftDiscord/Channel/DiscordMessage.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index 8dbad3bac..16bcb4e86 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -877,6 +877,13 @@ public struct DiscordAllowedMentions : Encodable { public let users: [UserID] /// For replies, whether to mention the author of the message being replied to (default: false) public let repliedUser: Bool + + public init(parse: DiscordAllowedMentionType = .everyone, roles: [RoleID] = [], users: [UserID] = [], repliedUser: Bool = false) { + self.parse = parse + self.roles = roles + self.users = users + self.repliedUser = repliedUser + } } /// A reference to a message, e.g. used in outgoing replies. @@ -890,4 +897,10 @@ public struct DiscordMessageReference : Encodable { public let messageId: MessageID? public let channelId: ChannelID? public let guildId: GuildID? + + public init(messageId: MessageID? = nil, channelId: ChannelID? = nil, guildId: GuildID? = nil) { + self.messageId = messageId + self.channelId = channelId + self.guildId = guildId + } } From aa3ba13cf1a3010da7106d9721b6b53b7271cd86 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 18:36:01 +0100 Subject: [PATCH 071/104] Add support for message stickers --- .../SwiftDiscord/Channel/DiscordMessage.swift | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index 16bcb4e86..2bac52755 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -97,6 +97,9 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { /// The reactions a message has. public let reactions: [DiscordReaction] + /// The stickers a message has. + public let stickers: [DiscordMessageSticker] + /// The timestamp of this message. public let timestamp: Date @@ -151,6 +154,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { nonce = messageObject.getSnowflake(key: "nonce") pinned = messageObject.get("pinned", or: false) reactions = DiscordReaction.reactionsFromArray(messageObject.get("reactions", or: [])) + stickers = DiscordMessageSticker.stickersFromArray(messageObject.get("sticker", or: [])) tts = messageObject.get("tts", or: false) editedTimestamp = DiscordDateFormatter.format(messageObject.get("edited_timestamp", or: "")) ?? Date() timestamp = DiscordDateFormatter.format(messageObject.get("timestamp", or: "")) ?? Date() @@ -194,6 +198,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { self.nonce = 0 self.pinned = false self.reactions = [] + self.stickers = [] self.editedTimestamp = Date() self.timestamp = Date() self.type = .default @@ -904,3 +909,43 @@ public struct DiscordMessageReference : Encodable { self.guildId = guildId } } + +public enum DiscordMessageStickerFormatType: Int { + case png = 1 + case apng = 2 + case lottie = 3 +} + +public struct DiscordMessageSticker { + /// ID of the sticker + public let id: Snowflake + /// ID of the sticker pack + public let packId: Snowflake + /// Name of the sticker + public let name: String + /// Description of the sticker + public let description: String + /// List of tags for the sticker + public let tags: [String] + /// Sticker asset hash + public let asset: String? + /// Sticker preview asset hash + public let previewAsset: String? + /// Type of sticker format + public let formatType: DiscordMessageStickerFormatType? + + init(stickerObject: [String: Any]) { + id = stickerObject.getSnowflake(key: "id") + packId = stickerObject.getSnowflake(key: "pack_id") + name = stickerObject.get("name", or: "") + description = stickerObject.get("description", or: "") + tags = stickerObject.get("tags", or: "").split(separator: ",").map(String.init) + asset = stickerObject.get("asset", as: String.self) + previewAsset = stickerObject.get("preview_asset", as: String.self) + formatType = stickerObject.get("format_type", as: Int.self).flatMap(DiscordMessageStickerFormatType.init(rawValue:)) + } + + static func stickersFromArray(_ stickerArray: [[String: Any]]) -> [DiscordMessageSticker] { + return stickerArray.map(DiscordMessageSticker.init) + } +} From 7a43c44ff1b59528ae75fb646defa54166017eb5 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 18:46:26 +0100 Subject: [PATCH 072/104] Migrate to new string-serialized permissions --- Sources/SwiftDiscord/Rest/DiscordEndpoint.swift | 2 +- .../SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift | 2 +- Sources/SwiftDiscord/User/DiscordPermission.swift | 7 +++++-- Sources/SwiftDiscord/User/DiscordRole.swift | 2 +- Sources/SwiftDiscord/User/DiscordUserGuild.swift | 2 +- Tests/SwiftDiscordTests/Fixtures.swift | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 89e4e81d0..4a9e9891d 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -582,7 +582,7 @@ public extension DiscordEndpoint { case name(String) /// The permissions this role has. - case permissions(Int) + case permissions(DiscordPermission) } /// Options for getting messages diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift index a40e1b4db..c896e8e10 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Guilds.swift @@ -110,7 +110,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { case let .name(name): roleData["name"] = name case let .permissions(permissions): - roleData["permissions"] = permissions + roleData["permissions"] = permissions.rawValue.description } } diff --git a/Sources/SwiftDiscord/User/DiscordPermission.swift b/Sources/SwiftDiscord/User/DiscordPermission.swift index b5cd16635..841acb54d 100644 --- a/Sources/SwiftDiscord/User/DiscordPermission.swift +++ b/Sources/SwiftDiscord/User/DiscordPermission.swift @@ -17,6 +17,9 @@ /// Represents a Discord Permission. Calculating Permissions involves bitwise operations. public struct DiscordPermission : OptionSet, Encodable { + // TODO: Migrate to BigInt or similar since permission are string-serialized + // and may have arbitrary size as of v8 + public let rawValue: Int /// This user can create invites. @@ -138,8 +141,8 @@ public struct DiscordPermissionOverwrite : Encodable { init(permissionOverwriteObject: [String: Any]) { id = permissionOverwriteObject.getSnowflake() type = DiscordPermissionOverwriteType(rawValue: permissionOverwriteObject.get("type", or: "")) ?? .role - allow = DiscordPermission(rawValue: permissionOverwriteObject.get("allow", or: 0)) - deny = DiscordPermission(rawValue: permissionOverwriteObject.get("deny", or: 0)) + allow = DiscordPermission(rawValue: Int(permissionOverwriteObject.get("allow", or: "0")) ?? 0) + deny = DiscordPermission(rawValue: Int(permissionOverwriteObject.get("deny", or: "0")) ?? 0) } static func overwritesFromArray(_ permissionOverwritesArray: [[String: Any]]) -> [OverwriteID: DiscordPermissionOverwrite] { diff --git a/Sources/SwiftDiscord/User/DiscordRole.swift b/Sources/SwiftDiscord/User/DiscordRole.swift index fc594c3b1..ce23ac2a8 100644 --- a/Sources/SwiftDiscord/User/DiscordRole.swift +++ b/Sources/SwiftDiscord/User/DiscordRole.swift @@ -50,7 +50,7 @@ public struct DiscordRole : Encodable, Equatable { managed = roleObject.get("managed", or: false) mentionable = roleObject.get("mentionable", or: false) name = roleObject.get("name", or: "") - permissions = DiscordPermission(rawValue: roleObject.get("permissions", or: 0)) + permissions = DiscordPermission(rawValue: Int(roleObject.get("permissions", or: "0")) ?? 0) position = roleObject.get("position", or: 0) } diff --git a/Sources/SwiftDiscord/User/DiscordUserGuild.swift b/Sources/SwiftDiscord/User/DiscordUserGuild.swift index 9088ef2b8..170e12add 100644 --- a/Sources/SwiftDiscord/User/DiscordUserGuild.swift +++ b/Sources/SwiftDiscord/User/DiscordUserGuild.swift @@ -39,7 +39,7 @@ public struct DiscordUserGuild { name = userGuildObject.get("name", or: "") icon = userGuildObject.get("icon", or: "") owner = userGuildObject.get("owner", or: false) - permissions = DiscordPermission(rawValue: userGuildObject.get("permissions", or: 0)) + permissions = DiscordPermission(rawValue: Int(userGuildObject.get("permissions", or: "0")) ?? 0) } static func userGuildsFromArray(_ guilds: [[String: Any]]) -> [GuildID: DiscordUserGuild] { diff --git a/Tests/SwiftDiscordTests/Fixtures.swift b/Tests/SwiftDiscordTests/Fixtures.swift index b86aeb554..f2a77ce3d 100644 --- a/Tests/SwiftDiscordTests/Fixtures.swift +++ b/Tests/SwiftDiscordTests/Fixtures.swift @@ -16,7 +16,7 @@ let testRole: [String: Any] = [ "managed": false, "mentionable": true, "name": "My Test Role", - "permissions": 0, + "permissions": "0", "position": 0 ] From a3446f268c62dad2e53fed8bbc681158fe2f1241 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 20 Nov 2020 18:50:32 +0100 Subject: [PATCH 073/104] Encode permissions as string --- Sources/SwiftDiscord/User/DiscordPermission.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/SwiftDiscord/User/DiscordPermission.swift b/Sources/SwiftDiscord/User/DiscordPermission.swift index 841acb54d..b583fc82a 100644 --- a/Sources/SwiftDiscord/User/DiscordPermission.swift +++ b/Sources/SwiftDiscord/User/DiscordPermission.swift @@ -93,6 +93,11 @@ public struct DiscordPermission : OptionSet, Encodable { public init(rawValue: Int) { self.rawValue = rawValue } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue.description) + } } /// Represents a permission overwrite type for a channel. From a524ea9f58e17f697b28a7d6b705703df7075b27 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 16:17:17 +0100 Subject: [PATCH 074/104] Implement support for interaction events --- Sources/SwiftDiscord/DiscordClient.swift | 15 ++++ .../SwiftDiscord/DiscordClientDelegate.swift | 8 ++ Sources/SwiftDiscord/DiscordSnowflakeID.swift | 3 + .../SwiftDiscord/Gateway/DiscordEvent.swift | 5 ++ .../Interaction/DiscordInteraction.swift | 83 +++++++++++++++++++ 5 files changed, 114 insertions(+) create mode 100644 Sources/SwiftDiscord/Interaction/DiscordInteraction.swift diff --git a/Sources/SwiftDiscord/DiscordClient.swift b/Sources/SwiftDiscord/DiscordClient.swift index a2d28c03c..58c2e35f7 100644 --- a/Sources/SwiftDiscord/DiscordClient.swift +++ b/Sources/SwiftDiscord/DiscordClient.swift @@ -242,6 +242,7 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco case .channelUpdate: handleChannelUpdate(with: eventData) case .channelCreate: handleChannelCreate(with: eventData) case .channelDelete: handleChannelDelete(with: eventData) + case .interactionCreate: handleInteractionCreate(with: eventData) case .voiceServerUpdate: handleVoiceServerUpdate(with: eventData) case .voiceStateUpdate: handleVoiceStateUpdate(with: eventData) case .ready: handleReady(with: eventData) @@ -861,6 +862,20 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco guild.updateGuild(fromPresence: presence!, fillingUsers: fillUsers, pruningUsers: pruneUsers) } + /// + /// Handles interaction creations from Discord, i.e. slash command + /// invocations. You shouldn't need to call this method directly. + /// + /// Override to provide additional customization around this event. + /// + /// Calls the `didCreateInteraction` delegate method. + /// + /// - parameter with: The data from the event + /// + open func handleInteractionCreate(with data: [String: Any]) { + // TODO + } + /// /// Handles the ready event from Discord. You shouldn't need to call this method directly. /// diff --git a/Sources/SwiftDiscord/DiscordClientDelegate.swift b/Sources/SwiftDiscord/DiscordClientDelegate.swift index 126879fc5..6606b729a 100644 --- a/Sources/SwiftDiscord/DiscordClientDelegate.swift +++ b/Sources/SwiftDiscord/DiscordClientDelegate.swift @@ -161,6 +161,14 @@ public protocol DiscordClientDelegate : class { /// func client(_ client: DiscordClient, didReceivePresenceUpdate presence: DiscordPresence) + /// + /// Called when the client receives a new interaction, i.e. + /// a slash command invocation. + /// + /// - parameter interaction: The invocation data + /// + func client(_ client: DiscordClient, didCreateInteraction interaction: DiscordInteraction) + /// /// Called when the client receives a ready event. /// diff --git a/Sources/SwiftDiscord/DiscordSnowflakeID.swift b/Sources/SwiftDiscord/DiscordSnowflakeID.swift index 7a9558616..96b954fa9 100644 --- a/Sources/SwiftDiscord/DiscordSnowflakeID.swift +++ b/Sources/SwiftDiscord/DiscordSnowflakeID.swift @@ -72,6 +72,9 @@ public typealias IntegrationID = Snowflake /// A Snowflake ID representing an Attachment public typealias AttachmentID = Snowflake +/// A Snowflake ID representing an Interaction +public typealias InteractionID = Snowflake + // MARK: Extra snowflake information extension Snowflake { diff --git a/Sources/SwiftDiscord/Gateway/DiscordEvent.swift b/Sources/SwiftDiscord/Gateway/DiscordEvent.swift index 8301b1fb7..4882e03b9 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEvent.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEvent.swift @@ -127,4 +127,9 @@ public enum DiscordDispatchEvent : String { /// Webhooks Update (Not handled) case webhooksUpdate = "WEBHOOKS_UPDATE" + + // Interactions + + /// Interaction Create (Handled) + case interactionCreate = "INTERACTION_CREATE" } diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift new file mode 100644 index 000000000..ce2813ac8 --- /dev/null +++ b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift @@ -0,0 +1,83 @@ +import Foundation + +/// Represents a slash-command invocation by the user. +public struct DiscordInteraction { + // MARK: Properties + + /// ID of the interaction + public let id: InteractionID + + /// Type of the interaction + public let type: DiscordInteractionType? + + /// Command data payload + /// Always specified for ApplicationCommand interaction + /// types, but optional for future-proofing. + public let data: ApplicationCommandInteractionData? + + /// Guild it was sent from + public let guildId: GuildID + + /// Channel it was sent from + public let channelId: ChannelID + + /// Guild member data for the invoking user + public let member: DiscordGuildMember? + + /// Continuation token for responding to the interaction + public let token: String + + /// Read-only property, always 1 + public let version: Int + + init(interactionObject: [String: Any]) { + id = Snowflake(interactionObject["id"] as? String) ?? 0 + type = (interactionObject["type"] as? Int).flatMap(DiscordInteractionType.init(rawValue:)) + data = (interactionObject["data"] as? [String: Any]).map(ApplicationCommandInteractionData.init(dataObject:)) + let guildId = Snowflake(interactionObject["guild_id"] as? String) ?? 0 + self.guildId = guildId + channelId = Snowflake(interactionObject["channel_id"] as? String) ?? 0 + member = (interactionObject["member"] as? [String: Any]).map { DiscordGuildMember(guildMemberObject: $0, guildId: guildId) } + token = (interactionObject["token"] as? String) ?? "" + version = (interactionObject["version"] as? Int) ?? 1 + } +} + +public enum DiscordInteractionType: Int { + case ping = 1 + case applicationCommand = 2 +} + +public struct ApplicationCommandInteractionData { + /// The ID of the invoked command + public let id: Snowflake + + /// The name of the invoked command + public let name: String + + /// The params + values by the user + public let options: [ApplicationCommandInteractionDataOption] + + init(dataObject: [String: Any]) { + id = Snowflake(dataObject["id"] as? String) ?? 0 + name = dataObject.get("name", as: String.self) ?? "" + options = (dataObject["options"] as? [[String: Any]])?.map(ApplicationCommandInteractionDataOption.init(optionObject:)).compactMap { $0 } ?? [] + } +} + +public struct ApplicationCommandInteractionDataOption { + /// The name of the parameter. + public let name: String + + /// The value of the pair. Type is the OptionType of the command. + public let value: Any? + + /// Present if this option is a group or subcommand. + public let options: [ApplicationCommandInteractionDataOption]? + + init(optionObject: [String: Any]) { + name = optionObject.get("name", as: String.self) ?? "" + value = optionObject["value"] + options = (optionObject["options"] as? [[String: Any]])?.map(ApplicationCommandInteractionDataOption.init(optionObject:)).compactMap { $0 } ?? [] + } +} From a05c1b315ee04258fcf484340f5036c255417cf4 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 16:38:20 +0100 Subject: [PATCH 075/104] Add application command REST endpoints --- Sources/SwiftDiscord/DiscordSnowflakeID.swift | 6 +++ .../SwiftDiscord/Rest/DiscordEndpoint.swift | 38 ++++++++++++++++++- .../Rest/DiscordRateLimiter.swift | 8 +++- .../TestDiscordDataStructures.swift | 2 +- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftDiscord/DiscordSnowflakeID.swift b/Sources/SwiftDiscord/DiscordSnowflakeID.swift index 96b954fa9..3d96ae1fc 100644 --- a/Sources/SwiftDiscord/DiscordSnowflakeID.swift +++ b/Sources/SwiftDiscord/DiscordSnowflakeID.swift @@ -75,6 +75,12 @@ public typealias AttachmentID = Snowflake /// A Snowflake ID representing an Interaction public typealias InteractionID = Snowflake +/// A Snowflake ID representing an Application +public typealias ApplicationID = Snowflake + +/// A Snowflake ID representing a Slash Command +public typealias CommandID = Snowflake + // MARK: Extra snowflake information extension Snowflake { diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index 4a9e9891d..dd287da93 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -160,6 +160,21 @@ public enum DiscordEndpoint : CustomStringConvertible { case guildEmoji(guild: GuildID, emoji: EmojiID) /* End Emoji */ + + /* Applications */ + /// The global slash-commands endpoint. + case globalApplicationCommands(applicationId: ApplicationID) + + /// The endpoint for a specific global slash-command. + case globalApplicationCommand(applicationId: ApplicationID, commandId: CommandID) + + /// The guild-specific slash-commands endpoint. + case guildApplicationCommands(applicationId: ApplicationID, guildId: GuildID) + + /// The endpoint for a specific guild-specific slash-command. + case guildApplicationCommand(applicationId: ApplicationID, guildId: GuildID, commandId: CommandID) + /* End Application */ + var combined: String { return DiscordEndpoint.baseURL.description + description } @@ -369,6 +384,17 @@ public extension DiscordEndpoint { case let .guildEmoji(guild, emoji): return "/guilds/\(guild)/emojis/\(emoji)" /* End Emoji */ + + /* Application */ + case let .globalApplicationCommands(applicationId): + return "/applications/\(applicationId)/commands" + case let .globalApplicationCommand(applicationId, commandId): + return "/applications/\(applicationId)/commands/\(commandId)" + case let .guildApplicationCommands(applicationId, guildId): + return "/applications/\(applicationId)/guilds/\(guildId)/commands" + case let .guildApplicationCommand(applicationId, guildId, commandId): + return "/applications/\(applicationId)/guilds/\(guildId)/commands/\(commandId)" + /* End Application */ } } @@ -478,7 +504,17 @@ public extension DiscordEndpoint { return DiscordRateLimitKey(id: guild, urlParts: [.guilds, .guildID, .emojis]) case let .guildEmoji(guild, _): return DiscordRateLimitKey(id: guild, urlParts: [.guilds, .guildID, .emojis, .emojiID]) - /* End Emoji */ + + /* Applications */ + case let .globalApplicationCommands(applicationId): + return DiscordRateLimitKey(id: applicationId, urlParts: [.applications, .applicationID, .commands]) + case let .globalApplicationCommand(applicationId, _): + return DiscordRateLimitKey(id: applicationId, urlParts: [.applications, .applicationID, .commands, .commandID]) + case let .guildApplicationCommands(applicationId, _): + return DiscordRateLimitKey(id: applicationId, urlParts: [.applications, .applicationID, .guilds, .guildID, .commands]) + case let .guildApplicationCommand(applicationId, _, _): + return DiscordRateLimitKey(id: applicationId, urlParts: [.applications, .applicationID, .guilds, .guildID, .commands, .commandID]) + /* End Applications */ } } diff --git a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift index 05bd54289..4f259915f 100644 --- a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift +++ b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift @@ -224,7 +224,7 @@ public struct DiscordRateLimitKey : Hashable { /// stored separately if needed. Technically, the .guildID and .channelID fields aren't needed since /// the full ID will also be stored, but they're included to make the system more straightforward. public struct DiscordRateLimitURLParts : OptionSet { - public let rawValue: Int + public let rawValue: Int64 static let guilds = DiscordRateLimitURLParts(rawValue: 1 << 0) static let guildID = DiscordRateLimitURLParts(rawValue: 1 << 1) @@ -257,8 +257,12 @@ public struct DiscordRateLimitKey : Hashable { static let emojis = DiscordRateLimitURLParts(rawValue: 1 << 28) static let emojiID = DiscordRateLimitURLParts(rawValue: 1 << 29) static let me = DiscordRateLimitURLParts(rawValue: 1 << 30) + static let applications = DiscordRateLimitURLParts(rawValue: 1 << 31) + static let applicationID = DiscordRateLimitURLParts(rawValue: 1 << 32) + static let commands = DiscordRateLimitURLParts(rawValue: 1 << 33) + static let commandID = DiscordRateLimitURLParts(rawValue: 1 << 34) - public init(rawValue: Int) { + public init(rawValue: Int64) { self.rawValue = rawValue } } diff --git a/Tests/SwiftDiscordTests/TestDiscordDataStructures.swift b/Tests/SwiftDiscordTests/TestDiscordDataStructures.swift index 53d617fd6..59038f485 100644 --- a/Tests/SwiftDiscordTests/TestDiscordDataStructures.swift +++ b/Tests/SwiftDiscordTests/TestDiscordDataStructures.swift @@ -34,7 +34,7 @@ public class TestDiscordDataStructures : XCTestCase { XCTAssertEqual(role1.hoist, role2.hoist, "Hoist should survive JSONification") XCTAssertEqual(role1.managed, role2.managed, "Managed should survive JSONification") XCTAssertEqual(role1.mentionable, role2.mentionable, "Mentionable should survive JSONification") - XCTAssertEqual(role1.name, role2.name, "Name should survive JSONificaiton") + XCTAssertEqual(role1.name, role2.name, "Name should survive JSONification") XCTAssertEqual(role1.permissions, role2.permissions, "Permissions should survive JSONification") XCTAssertEqual(role1.position, role2.position, "Position should survive JSONification") } From 00cfc1bdf7788ed3d8b172503dd21671398c3c4f Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 16:43:54 +0100 Subject: [PATCH 076/104] Add interaction callback endpoint --- .../SwiftDiscord/Rest/DiscordEndpoint.swift | 14 ++++ .../Rest/DiscordRateLimiter.swift | 74 ++++++++++--------- 2 files changed, 53 insertions(+), 35 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift index dd287da93..06496c1ab 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpoint.swift @@ -175,6 +175,10 @@ public enum DiscordEndpoint : CustomStringConvertible { case guildApplicationCommand(applicationId: ApplicationID, guildId: GuildID, commandId: CommandID) /* End Application */ + /* Interactions */ + case interactionsCallback(interactionId: InteractionID, interactionToken: String) + /* End Interactions */ + var combined: String { return DiscordEndpoint.baseURL.description + description } @@ -395,6 +399,11 @@ public extension DiscordEndpoint { case let .guildApplicationCommand(applicationId, guildId, commandId): return "/applications/\(applicationId)/guilds/\(guildId)/commands/\(commandId)" /* End Application */ + + /* Interactions */ + case let .interactionsCallback(interactionId, interactionToken): + return "/interactions/\(interactionId)/\(interactionToken)/callback" + /* End Interactions */ } } @@ -515,6 +524,11 @@ public extension DiscordEndpoint { case let .guildApplicationCommand(applicationId, _, _): return DiscordRateLimitKey(id: applicationId, urlParts: [.applications, .applicationID, .guilds, .guildID, .commands, .commandID]) /* End Applications */ + + /* Interactions */ + case let .interactionsCallback(interactionId, _): + return DiscordRateLimitKey(id: interactionId, urlParts: [.interactions, .interactionID, .interactionToken, .callback]) + /* End Interactions */ } } diff --git a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift index 4f259915f..7f5546b52 100644 --- a/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift +++ b/Sources/SwiftDiscord/Rest/DiscordRateLimiter.swift @@ -226,41 +226,45 @@ public struct DiscordRateLimitKey : Hashable { public struct DiscordRateLimitURLParts : OptionSet { public let rawValue: Int64 - static let guilds = DiscordRateLimitURLParts(rawValue: 1 << 0) - static let guildID = DiscordRateLimitURLParts(rawValue: 1 << 1) - static let channels = DiscordRateLimitURLParts(rawValue: 1 << 2) - static let channelID = DiscordRateLimitURLParts(rawValue: 1 << 3) - static let messages = DiscordRateLimitURLParts(rawValue: 1 << 4) - static let messagesDelete = DiscordRateLimitURLParts(rawValue: 1 << 5) - static let messageID = DiscordRateLimitURLParts(rawValue: 1 << 6) - static let bulkDelete = DiscordRateLimitURLParts(rawValue: 1 << 7) - static let typing = DiscordRateLimitURLParts(rawValue: 1 << 8) - static let permissions = DiscordRateLimitURLParts(rawValue: 1 << 9) - static let overwriteID = DiscordRateLimitURLParts(rawValue: 1 << 10) - static let invites = DiscordRateLimitURLParts(rawValue: 1 << 11) - static let inviteCode = DiscordRateLimitURLParts(rawValue: 1 << 12) - static let pins = DiscordRateLimitURLParts(rawValue: 1 << 13) - static let webhooks = DiscordRateLimitURLParts(rawValue: 1 << 14) - static let members = DiscordRateLimitURLParts(rawValue: 1 << 15) - static let userID = DiscordRateLimitURLParts(rawValue: 1 << 16) - static let roles = DiscordRateLimitURLParts(rawValue: 1 << 17) - static let roleID = DiscordRateLimitURLParts(rawValue: 1 << 18) - static let bans = DiscordRateLimitURLParts(rawValue: 1 << 19) - static let users = DiscordRateLimitURLParts(rawValue: 1 << 20) - static let webhookID = DiscordRateLimitURLParts(rawValue: 1 << 21) - static let webhookToken = DiscordRateLimitURLParts(rawValue: 1 << 22) - static let slack = DiscordRateLimitURLParts(rawValue: 1 << 23) - static let github = DiscordRateLimitURLParts(rawValue: 1 << 24) - static let auditLog = DiscordRateLimitURLParts(rawValue: 1 << 25) - static let reactions = DiscordRateLimitURLParts(rawValue: 1 << 26) - static let emoji = DiscordRateLimitURLParts(rawValue: 1 << 27) - static let emojis = DiscordRateLimitURLParts(rawValue: 1 << 28) - static let emojiID = DiscordRateLimitURLParts(rawValue: 1 << 29) - static let me = DiscordRateLimitURLParts(rawValue: 1 << 30) - static let applications = DiscordRateLimitURLParts(rawValue: 1 << 31) - static let applicationID = DiscordRateLimitURLParts(rawValue: 1 << 32) - static let commands = DiscordRateLimitURLParts(rawValue: 1 << 33) - static let commandID = DiscordRateLimitURLParts(rawValue: 1 << 34) + static let guilds = DiscordRateLimitURLParts(rawValue: 1 << 0) + static let guildID = DiscordRateLimitURLParts(rawValue: 1 << 1) + static let channels = DiscordRateLimitURLParts(rawValue: 1 << 2) + static let channelID = DiscordRateLimitURLParts(rawValue: 1 << 3) + static let messages = DiscordRateLimitURLParts(rawValue: 1 << 4) + static let messagesDelete = DiscordRateLimitURLParts(rawValue: 1 << 5) + static let messageID = DiscordRateLimitURLParts(rawValue: 1 << 6) + static let bulkDelete = DiscordRateLimitURLParts(rawValue: 1 << 7) + static let typing = DiscordRateLimitURLParts(rawValue: 1 << 8) + static let permissions = DiscordRateLimitURLParts(rawValue: 1 << 9) + static let overwriteID = DiscordRateLimitURLParts(rawValue: 1 << 10) + static let invites = DiscordRateLimitURLParts(rawValue: 1 << 11) + static let inviteCode = DiscordRateLimitURLParts(rawValue: 1 << 12) + static let pins = DiscordRateLimitURLParts(rawValue: 1 << 13) + static let webhooks = DiscordRateLimitURLParts(rawValue: 1 << 14) + static let members = DiscordRateLimitURLParts(rawValue: 1 << 15) + static let userID = DiscordRateLimitURLParts(rawValue: 1 << 16) + static let roles = DiscordRateLimitURLParts(rawValue: 1 << 17) + static let roleID = DiscordRateLimitURLParts(rawValue: 1 << 18) + static let bans = DiscordRateLimitURLParts(rawValue: 1 << 19) + static let users = DiscordRateLimitURLParts(rawValue: 1 << 20) + static let webhookID = DiscordRateLimitURLParts(rawValue: 1 << 21) + static let webhookToken = DiscordRateLimitURLParts(rawValue: 1 << 22) + static let slack = DiscordRateLimitURLParts(rawValue: 1 << 23) + static let github = DiscordRateLimitURLParts(rawValue: 1 << 24) + static let auditLog = DiscordRateLimitURLParts(rawValue: 1 << 25) + static let reactions = DiscordRateLimitURLParts(rawValue: 1 << 26) + static let emoji = DiscordRateLimitURLParts(rawValue: 1 << 27) + static let emojis = DiscordRateLimitURLParts(rawValue: 1 << 28) + static let emojiID = DiscordRateLimitURLParts(rawValue: 1 << 29) + static let me = DiscordRateLimitURLParts(rawValue: 1 << 30) + static let applications = DiscordRateLimitURLParts(rawValue: 1 << 31) + static let applicationID = DiscordRateLimitURLParts(rawValue: 1 << 32) + static let commands = DiscordRateLimitURLParts(rawValue: 1 << 33) + static let commandID = DiscordRateLimitURLParts(rawValue: 1 << 34) + static let interactions = DiscordRateLimitURLParts(rawValue: 1 << 35) + static let interactionID = DiscordRateLimitURLParts(rawValue: 1 << 36) + static let interactionToken = DiscordRateLimitURLParts(rawValue: 1 << 37) + static let callback = DiscordRateLimitURLParts(rawValue: 1 << 38) public init(rawValue: Int64) { self.rawValue = rawValue From 61b4beb34493735131ef649d9f7f565c1fd58e60 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 17:05:04 +0100 Subject: [PATCH 077/104] Add application command structures --- .../DiscordApplicationCommand.swift | 120 ++++++++++++++++++ .../Interaction/DiscordInteraction.swift | 42 +----- 2 files changed, 124 insertions(+), 38 deletions(-) create mode 100644 Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift diff --git a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift new file mode 100644 index 000000000..016eedfe7 --- /dev/null +++ b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift @@ -0,0 +1,120 @@ +import Foundation + +/// Represents a slash-command. The base command model of the +/// application. +public struct DiscordApplicationCommand: Encodable { + // MARK: Properties + + /// The ID of the command. + public let id: CommandID + + /// The ID of the parent application. + public let applicationId: ApplicationID + + /// 3-32 character name + public let name: String + + /// 1-100 character description + public let description: String + + /// The parameters for the command + public let parameters: DiscordApplicationCommandOption +} + +public struct DiscordApplicationCommandOption: Encodable { + /// The expected type + public let type: DiscordApplicationCommandOptionType + + /// 1-32 character name + public let name: String + + /// 1-100 character description + public let description: String + + /// The first required option for the user to complete + /// Only one option can be default + public let isDefault: Bool? + + /// If the parameter is required or optional, default is false + public let isRequired: Bool? + + /// Choices for string and int types for the user to pick from + public let choices: [DiscordApplicationCommandOptionChoice]? + + /// If the option is a subcommand or subcommand group, these + /// nested options will be the parameters + public let options: [DiscordApplicationCommandOption] +} + +public struct DiscordApplicationCommandOptionChoice: Encodable { + /// 1-100 character choice name + public let name: String + + /// Value of the choice + public let value: DiscordApplicationCommandOptionChoiceValue +} + +public enum DiscordApplicationCommandOptionChoiceValue: Encodable { + case string(String) + case int(Int) + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let s): + try container.encode(s) + case .int(let i): + try container.encode(i) + } + } +} + +public enum DiscordApplicationCommandOptionType: Int, Encodable { + case subCommand = 1 + case subCommandGroup = 2 + case string = 3 + case integer = 4 + case boolean = 5 + case user = 6 + case channel = 7 + case role = 8 + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } +} + +public struct DiscordApplicationCommandInteractionData { + /// The ID of the invoked command + public let id: CommandID + + /// The name of the invoked command + public let name: String + + /// The params + values by the user + public let options: [DiscordApplicationCommandInteractionDataOption] + + init(dataObject: [String: Any]) { + id = Snowflake(dataObject["id"] as? String) ?? 0 + name = dataObject.get("name", as: String.self) ?? "" + options = (dataObject["options"] as? [[String: Any]])?.map(DiscordApplicationCommandInteractionDataOption.init(optionObject:)).compactMap { $0 } ?? [] + } +} + +public struct DiscordApplicationCommandInteractionDataOption { + /// The name of the parameter. + public let name: String + + /// The value of the pair. Type is the OptionType of the command. + public let value: Any? + + /// Present if this option is a group or subcommand. + public let options: [DiscordApplicationCommandInteractionDataOption]? + + init(optionObject: [String: Any]) { + name = optionObject.get("name", as: String.self) ?? "" + value = optionObject["value"] + options = (optionObject["options"] as? [[String: Any]])?.map(DiscordApplicationCommandInteractionDataOption.init(optionObject:)).compactMap { $0 } ?? [] + } +} diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift index ce2813ac8..fbe77c6c5 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift @@ -11,9 +11,9 @@ public struct DiscordInteraction { public let type: DiscordInteractionType? /// Command data payload - /// Always specified for ApplicationCommand interaction + /// Always specified for DiscordApplicationCommand interaction /// types, but optional for future-proofing. - public let data: ApplicationCommandInteractionData? + public let data: DiscordApplicationCommandInteractionData? /// Guild it was sent from public let guildId: GuildID @@ -33,7 +33,7 @@ public struct DiscordInteraction { init(interactionObject: [String: Any]) { id = Snowflake(interactionObject["id"] as? String) ?? 0 type = (interactionObject["type"] as? Int).flatMap(DiscordInteractionType.init(rawValue:)) - data = (interactionObject["data"] as? [String: Any]).map(ApplicationCommandInteractionData.init(dataObject:)) + data = (interactionObject["data"] as? [String: Any]).map(DiscordApplicationCommandInteractionData.init(dataObject:)) let guildId = Snowflake(interactionObject["guild_id"] as? String) ?? 0 self.guildId = guildId channelId = Snowflake(interactionObject["channel_id"] as? String) ?? 0 @@ -45,39 +45,5 @@ public struct DiscordInteraction { public enum DiscordInteractionType: Int { case ping = 1 - case applicationCommand = 2 -} - -public struct ApplicationCommandInteractionData { - /// The ID of the invoked command - public let id: Snowflake - - /// The name of the invoked command - public let name: String - - /// The params + values by the user - public let options: [ApplicationCommandInteractionDataOption] - - init(dataObject: [String: Any]) { - id = Snowflake(dataObject["id"] as? String) ?? 0 - name = dataObject.get("name", as: String.self) ?? "" - options = (dataObject["options"] as? [[String: Any]])?.map(ApplicationCommandInteractionDataOption.init(optionObject:)).compactMap { $0 } ?? [] - } -} - -public struct ApplicationCommandInteractionDataOption { - /// The name of the parameter. - public let name: String - - /// The value of the pair. Type is the OptionType of the command. - public let value: Any? - - /// Present if this option is a group or subcommand. - public let options: [ApplicationCommandInteractionDataOption]? - - init(optionObject: [String: Any]) { - name = optionObject.get("name", as: String.self) ?? "" - value = optionObject["value"] - options = (optionObject["options"] as? [[String: Any]])?.map(ApplicationCommandInteractionDataOption.init(optionObject:)).compactMap { $0 } ?? [] - } + case DiscordapplicationCommand = 2 } From a8879cd7166f8246b10492e63ff514e7ab7c357c Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 17:06:14 +0100 Subject: [PATCH 078/104] Add correct coding keys to application command structures --- .../DiscordApplicationCommand.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift index 016eedfe7..a3816a701 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift @@ -3,6 +3,14 @@ import Foundation /// Represents a slash-command. The base command model of the /// application. public struct DiscordApplicationCommand: Encodable { + public enum CodingKeys: String, CodingKey { + case id + case applicationId = "application_id" + case name + case description + case parameters + } + // MARK: Properties /// The ID of the command. @@ -22,6 +30,16 @@ public struct DiscordApplicationCommand: Encodable { } public struct DiscordApplicationCommandOption: Encodable { + public enum CodingKeys: String, CodingKey { + case type + case name + case description + case isDefault = "default" + case isRequired = "required" + case choices + case options + } + /// The expected type public let type: DiscordApplicationCommandOptionType From 9ecc8b80ca8426983d7453443d886233d3e515a4 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 17:34:24 +0100 Subject: [PATCH 079/104] Add application command decoders and stub out endpoint methods --- .../DiscordApplicationCommand.swift | 40 ++++++++++++++-- ...DiscordEndpointConsumer+Applications.swift | 48 +++++++++++++++++++ .../Rest/DiscordEndpointConsumer.swift | 34 +++++++++++++ 3 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift diff --git a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift index a3816a701..ea4c2dcb9 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift @@ -26,7 +26,19 @@ public struct DiscordApplicationCommand: Encodable { public let description: String /// The parameters for the command - public let parameters: DiscordApplicationCommandOption + public let parameters: [DiscordApplicationCommandOption] + + init(commandObject: [String: Any]) { + id = Snowflake((commandObject["id"] as? String) ?? "") + applicationId = Snowflake((commandObject["application_id"] as? String) ?? "") + name = (commandObject["name"] as? String) ?? "" + description = (commandObject["description"] as? String) ?? "" + parameters = ((commandObject["parameters"] as? [[String: Any]]) ?? []).map(DiscordApplicationCommandOption.init(optionObject:)) + } + + static func commandsFromArray(_ array: [[String: Any]]) -> [DiscordApplicationCommand] { + return array.map({ DiscordApplicationCommand(commandObject: $0) }) + } } public struct DiscordApplicationCommandOption: Encodable { @@ -41,7 +53,7 @@ public struct DiscordApplicationCommandOption: Encodable { } /// The expected type - public let type: DiscordApplicationCommandOptionType + public let type: DiscordApplicationCommandOptionType? /// 1-32 character name public let name: String @@ -61,7 +73,17 @@ public struct DiscordApplicationCommandOption: Encodable { /// If the option is a subcommand or subcommand group, these /// nested options will be the parameters - public let options: [DiscordApplicationCommandOption] + public let options: [DiscordApplicationCommandOption]? + + init(optionObject: [String: Any]) { + type = (optionObject["type"] as? Int).flatMap(DiscordApplicationCommandOptionType.init(rawValue:)) + name = (optionObject["name"] as? String) ?? "" + description = (optionObject["description"] as? String) ?? "" + isDefault = (optionObject["default"] as? Bool) ?? false + isRequired = (optionObject["required"] as? Bool) ?? false + choices = (optionObject["choices"] as? [[String: Any]]).map { $0.map(DiscordApplicationCommandOptionChoice.init(choiceObject:)) } + options = (optionObject["options"] as? [[String: Any]]).map { $0.map(DiscordApplicationCommandOption.init(optionObject:)) } + } } public struct DiscordApplicationCommandOptionChoice: Encodable { @@ -69,7 +91,17 @@ public struct DiscordApplicationCommandOptionChoice: Encodable { public let name: String /// Value of the choice - public let value: DiscordApplicationCommandOptionChoiceValue + public let value: DiscordApplicationCommandOptionChoiceValue? + + init(choiceObject: [String: Any]) { + name = (choiceObject["name"] as? String) ?? "" + let rawValue = choiceObject["value"] + if let value = rawValue as? String { + self.value = .string(value) + } else if let value = rawValue as? Int { + self.value = .int(value) + } + } } public enum DiscordApplicationCommandOptionChoiceValue: Encodable { diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift new file mode 100644 index 000000000..68f6646f1 --- /dev/null +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift @@ -0,0 +1,48 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +public extension DiscordEndpointConsumer where Self: DiscordUserActor { + /// Default implementation + func getApplicationCommands(callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) { + guard let applicationId = user?.id else { callback(nil, nil); return } + let requestCallback: DiscordRequestCallback = {data, response, error in + guard case let .array(commands)? = JSON.jsonFromResponse(data: data, response: response) else { + callback([], response) + return + } + + callback(DiscordApplicationCommand.commandsFromArray(commands as! [[String: Any]]), response) + } + rateLimiter.executeRequest(endpoint: .globalApplicationCommands(applicationId) + token: token, + requestInfo: .get(params: getParams, extraHeaders: nil), + callback: requestCallback) + } + + /// Default implementation + func getApplicationCommand(_ commandId: CommandID, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { + guard let applicationId = user?.id else { callback(nil, nil); return } + + // TODO + } + + /// Default implementation + func getApplicationCommands(on guildId: GuildID, + callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) { + guard let applicationId = user?.id else { callback([], nil); return } + + // TODO + } + + /// Default implementation + func getApplicationCommand(_ commandId: CommandID, + on guildId: GuildID, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { + guard let applicationId = user?.id else { callback(nil, nil); return } + + // TODO + } +} diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift index 6afbc0d9e..47dcd7c9d 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift @@ -625,6 +625,40 @@ public protocol DiscordEndpointConsumer { /// func getGuilds(callback: @escaping ([ChannelID: DiscordUserGuild], HTTPURLResponse?) -> ()) + // MARK: Applications + + /// + /// Gets the global slash-commands of a user. + /// + /// - parameter callback: The callback function, taking a dictionary of commands. + /// + func getApplicationCommands(callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) + + /// + /// Gets a global slash-command of a user. + /// + /// - parameter callback: The callback function, taking a command. + /// + func getApplicationCommand(_ commandId: CommandID, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + + /// + /// Gets the guild-specific slash-commands of a user. + /// + /// - parameter callback: The callback function, taking a dictionary of commands. + /// + func getApplicationCommands(on guildId: GuildID, + callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) + + /// + /// Gets a guild-specific slash-command of a user. + /// + /// - parameter callback: The callback function, taking a command. + /// + func getApplicationCommand(_ commandId: CommandID, + on guildId: GuildID, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + // MARK: Misc /// From b0abb7460e6cebe294c9d4bb14f128c4050dfca8 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 17:37:22 +0100 Subject: [PATCH 080/104] Fix some typos --- .../Interaction/DiscordApplicationCommand.swift | 10 ++++++---- .../Rest/DiscordEndpointConsumer+Applications.swift | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift index ea4c2dcb9..cdc77b224 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift @@ -29,8 +29,8 @@ public struct DiscordApplicationCommand: Encodable { public let parameters: [DiscordApplicationCommandOption] init(commandObject: [String: Any]) { - id = Snowflake((commandObject["id"] as? String) ?? "") - applicationId = Snowflake((commandObject["application_id"] as? String) ?? "") + id = Snowflake((commandObject["id"] as? String) ?? "") ?? 0 + applicationId = Snowflake((commandObject["application_id"] as? String) ?? "") ?? 0 name = (commandObject["name"] as? String) ?? "" description = (commandObject["description"] as? String) ?? "" parameters = ((commandObject["parameters"] as? [[String: Any]]) ?? []).map(DiscordApplicationCommandOption.init(optionObject:)) @@ -81,7 +81,7 @@ public struct DiscordApplicationCommandOption: Encodable { description = (optionObject["description"] as? String) ?? "" isDefault = (optionObject["default"] as? Bool) ?? false isRequired = (optionObject["required"] as? Bool) ?? false - choices = (optionObject["choices"] as? [[String: Any]]).map { $0.map(DiscordApplicationCommandOptionChoice.init(choiceObject:)) } + choices = (optionObject["choices"] as? [[String: Any]]).map { $0.compactMap(DiscordApplicationCommandOptionChoice.init(choiceObject:)) } options = (optionObject["options"] as? [[String: Any]]).map { $0.map(DiscordApplicationCommandOption.init(optionObject:)) } } } @@ -93,13 +93,15 @@ public struct DiscordApplicationCommandOptionChoice: Encodable { /// Value of the choice public let value: DiscordApplicationCommandOptionChoiceValue? - init(choiceObject: [String: Any]) { + init?(choiceObject: [String: Any]) { name = (choiceObject["name"] as? String) ?? "" let rawValue = choiceObject["value"] if let value = rawValue as? String { self.value = .string(value) } else if let value = rawValue as? Int { self.value = .int(value) + } else { + return nil } } } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift index 68f6646f1..a5bba3fd3 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift @@ -6,7 +6,7 @@ import FoundationNetworking public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation func getApplicationCommands(callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) { - guard let applicationId = user?.id else { callback(nil, nil); return } + guard let applicationId = user?.id else { callback([], nil); return } let requestCallback: DiscordRequestCallback = {data, response, error in guard case let .array(commands)? = JSON.jsonFromResponse(data: data, response: response) else { callback([], response) @@ -15,9 +15,9 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { callback(DiscordApplicationCommand.commandsFromArray(commands as! [[String: Any]]), response) } - rateLimiter.executeRequest(endpoint: .globalApplicationCommands(applicationId) + rateLimiter.executeRequest(endpoint: .globalApplicationCommands(applicationId: applicationId), token: token, - requestInfo: .get(params: getParams, extraHeaders: nil), + requestInfo: .get(params: nil, extraHeaders: nil), callback: requestCallback) } From 317c33ef1b867fdb43d1a4893a9121da1c0ff11a Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 17:40:48 +0100 Subject: [PATCH 081/104] Implement all get-commands endpoints --- ...DiscordEndpointConsumer+Applications.swift | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift index a5bba3fd3..63ecc93f9 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift @@ -25,16 +25,36 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { func getApplicationCommand(_ commandId: CommandID, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } + let requestCallback: DiscordRequestCallback = {data, response, error in + guard case let .object(command) = JSON.jsonFromResponse(data: data, response: response) else { + callback(nil, response) + return + } - // TODO + callback(DiscordApplicationCommand(commandObject: command), response) + } + rateLimiter.executeRequest(endpoint: .globalApplicationCommand(applicationId: applicationId, commandId: commandId), + token: token, + requestInfo: .get(params: nil, extraHeaders: nil), + callback: requestCallback) } /// Default implementation func getApplicationCommands(on guildId: GuildID, callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback([], nil); return } + let requestCallback: DiscordRequestCallback = {data, response, error in + guard case let .array(commands)? = JSON.jsonFromResponse(data: data, response: response) else { + callback([], response) + return + } - // TODO + callback(DiscordApplicationCommand.commandsFromArray(commands as! [[String: Any]]), response) + } + rateLimiter.executeRequest(endpoint: .guildApplicationCommands(applicationId: applicationId, guildId: guildId), + token: token, + requestInfo: .get(params: nil, extraHeaders: nil), + callback: requestCallback) } /// Default implementation @@ -42,7 +62,17 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { on guildId: GuildID, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } + let requestCallback: DiscordRequestCallback = {data, response, error in + guard case let .object(command) = JSON.jsonFromResponse(data: data, response: response) else { + callback(nil, response) + return + } - // TODO + callback(DiscordApplicationCommand(commandObject: command), response) + } + rateLimiter.executeRequest(endpoint: .guildApplicationCommand(applicationId: applicationId, guildId: guildId, commandId: commandId), + token: token, + requestInfo: .get(params: nil, extraHeaders: nil), + callback: requestCallback) } } From a900c0e55aeace096cd22a483a3d14db9d646765 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 17:50:44 +0100 Subject: [PATCH 082/104] Stub out other CRUD endpoints for commands --- ...DiscordEndpointConsumer+Applications.swift | 72 ++++++++++++------- .../Rest/DiscordEndpointConsumer.swift | 58 +++++++++++++-- 2 files changed, 96 insertions(+), 34 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift index 63ecc93f9..2a63d13bd 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift @@ -22,21 +22,29 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - func getApplicationCommand(_ commandId: CommandID, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { + func createApplicationCommand(name: String, + description: String, + options: [DiscordApplicationCommandOption]? = nil, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } - let requestCallback: DiscordRequestCallback = {data, response, error in - guard case let .object(command) = JSON.jsonFromResponse(data: data, response: response) else { - callback(nil, response) - return - } + // TODO + } - callback(DiscordApplicationCommand(commandObject: command), response) - } - rateLimiter.executeRequest(endpoint: .globalApplicationCommand(applicationId: applicationId, commandId: commandId), - token: token, - requestInfo: .get(params: nil, extraHeaders: nil), - callback: requestCallback) + /// Default implementation + func editApplicationCommand(_ commandId: CommandID, + name: String, + description: String, + options: [DiscordApplicationCommandOption]? = nil, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { + guard let applicationId = user?.id else { callback(nil, nil); return } + // TODO + } + + /// Default implementation + func deleteApplicationCommand(_ commandId: CommandID, + callback: @escaping (HTTPURLResponse?) -> ()) { + guard let applicationId = user?.id else { callback(nil); return } + // TODO } /// Default implementation @@ -58,21 +66,31 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } /// Default implementation - func getApplicationCommand(_ commandId: CommandID, - on guildId: GuildID, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { + func createApplicationCommand(on guildId: GuildID, + name: String, + description: String, + options: [DiscordApplicationCommandOption]? = nil, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } - let requestCallback: DiscordRequestCallback = {data, response, error in - guard case let .object(command) = JSON.jsonFromResponse(data: data, response: response) else { - callback(nil, response) - return - } + // TODO + } - callback(DiscordApplicationCommand(commandObject: command), response) - } - rateLimiter.executeRequest(endpoint: .guildApplicationCommand(applicationId: applicationId, guildId: guildId, commandId: commandId), - token: token, - requestInfo: .get(params: nil, extraHeaders: nil), - callback: requestCallback) + /// Default implementation + func editApplicationCommand(_ commandId: CommandID, + on guildId: GuildID, + name: String, + description: String, + options: [DiscordApplicationCommandOption]? = nil, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { + guard let applicationId = user?.id else { callback(nil, nil); return } + // TODO + } + + /// Default implementation + func deleteApplicationCommand(_ commandId: CommandID, + on guildId: GuildID, + callback: @escaping (HTTPURLResponse?) -> ()) { + guard let applicationId = user?.id else { callback(nil); return } + // TODO } } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift index 47dcd7c9d..9f27831d5 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift @@ -635,12 +635,33 @@ public protocol DiscordEndpointConsumer { func getApplicationCommands(callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) /// - /// Gets a global slash-command of a user. + /// Creates a global slash-command for a user. /// /// - parameter callback: The callback function, taking a command. /// - func getApplicationCommand(_ commandId: CommandID, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + func createApplicationCommand(name: String, + description: String, + options: [DiscordApplicationCommandOption]?, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + + /// + /// Edits a global slash-command for a user. + /// + /// - parameter callback: The callback function, taking a command. + /// + func editApplicationCommand(_ commandId: CommandID, + name: String, + description: String, + options: [DiscordApplicationCommandOption]?, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + + /// + /// Deletes a global slash-command for a user. + /// + /// - parameter callback: The callback function, taking a command. + /// + func deleteApplicationCommand(_ commandId: CommandID, + callback: @escaping (HTTPURLResponse?) -> ()) /// /// Gets the guild-specific slash-commands of a user. @@ -651,13 +672,36 @@ public protocol DiscordEndpointConsumer { callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) /// - /// Gets a guild-specific slash-command of a user. + /// Creates a guild-specific slash-command for a user. /// /// - parameter callback: The callback function, taking a command. /// - func getApplicationCommand(_ commandId: CommandID, - on guildId: GuildID, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + func createApplicationCommand(on guildId: GuildID, + name: String, + description: String, + options: [DiscordApplicationCommandOption]?, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + + /// + /// Edits a guild-specific slash-command for a user. + /// + /// - parameter callback: The callback function, taking a command. + /// + func editApplicationCommand(_ commandId: CommandID, + on guildId: GuildID, + name: String, + description: String, + options: [DiscordApplicationCommandOption]?, + callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + + /// + /// Deletes a guild-specific slash-command for a user. + /// + /// - parameter callback: The callback function, taking a command. + /// + func deleteApplicationCommand(_ commandId: CommandID, + on guildId: GuildID, + callback: @escaping (HTTPURLResponse?) -> ()) // MARK: Misc From e0f91d459d81fd0945d48fe29892d844a20adf54 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 17:57:34 +0100 Subject: [PATCH 083/104] Implement create command endpoint --- ...DiscordEndpointConsumer+Applications.swift | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift index 2a63d13bd..445fb3e57 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift @@ -3,6 +3,12 @@ import Foundation import FoundationNetworking #endif +private struct CommandParams: Encodable { + let name: String + let description: String + let options: [DiscordApplicationCommandOption]? +} + public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation func getApplicationCommands(callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) { @@ -27,7 +33,19 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { options: [DiscordApplicationCommandOption]? = nil, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } - // TODO + let requestCallback: DiscordRequestCallback = {data, response, error in + guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { + callback(nil, response) + return + } + + callback(DiscordApplicationCommand(commandObject: command), response) + } + let params = CommandParams(name: name, description: description, options: options) + rateLimiter.executeRequest(endpoint: .globalApplicationCommands(applicationId: applicationId), + token: token, + requestInfo: .post(content: .json(JSON.encodeJSONData(params) ?? Data()), extraHeaders: nil), + callback: requestCallback) } /// Default implementation @@ -37,6 +55,14 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { options: [DiscordApplicationCommandOption]? = nil, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } + let requestCallback: DiscordRequestCallback = {data, response, error in + guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { + callback(nil, response) + return + } + + callback(DiscordApplicationCommand(commandObject: command), response) + } // TODO } @@ -72,6 +98,14 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { options: [DiscordApplicationCommandOption]? = nil, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } + let requestCallback: DiscordRequestCallback = {data, response, error in + guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { + callback(nil, response) + return + } + + callback(DiscordApplicationCommand(commandObject: command), response) + } // TODO } @@ -83,6 +117,14 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { options: [DiscordApplicationCommandOption]? = nil, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } + let requestCallback: DiscordRequestCallback = {data, response, error in + guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { + callback(nil, response) + return + } + + callback(DiscordApplicationCommand(commandObject: command), response) + } // TODO } From 27fa4dc98a78d0b7c19ee2a0613c41ed948011a4 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 17:58:24 +0100 Subject: [PATCH 084/104] Implement edit command endpoint --- .../Rest/DiscordEndpointConsumer+Applications.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift index 445fb3e57..1e5bc76f5 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift @@ -63,7 +63,11 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { callback(DiscordApplicationCommand(commandObject: command), response) } - // TODO + let params = CommandParams(name: name, description: description, options: options) + rateLimiter.executeRequest(endpoint: .globalApplicationCommand(applicationId: applicationId, commandId: commandId), + token: token, + requestInfo: .patch(content: .json(JSON.encodeJSONData(params) ?? Data()), extraHeaders: nil), + callback: requestCallback) } /// Default implementation From 0f0409a4ba70f62146585baf1066e24dd261ed41 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 17:59:54 +0100 Subject: [PATCH 085/104] Implement command deletion endpoint --- ...DiscordEndpointConsumer+Applications.swift | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift index 1e5bc76f5..a4d87cb8e 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift @@ -13,7 +13,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation func getApplicationCommands(callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback([], nil); return } - let requestCallback: DiscordRequestCallback = {data, response, error in + let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(commands)? = JSON.jsonFromResponse(data: data, response: response) else { callback([], response) return @@ -33,7 +33,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { options: [DiscordApplicationCommandOption]? = nil, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } - let requestCallback: DiscordRequestCallback = {data, response, error in + let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { callback(nil, response) return @@ -55,7 +55,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { options: [DiscordApplicationCommandOption]? = nil, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } - let requestCallback: DiscordRequestCallback = {data, response, error in + let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { callback(nil, response) return @@ -74,14 +74,20 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { func deleteApplicationCommand(_ commandId: CommandID, callback: @escaping (HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil); return } - // TODO + let requestCallback: DiscordRequestCallback = { data, response, error in + callback(response) + } + rateLimiter.executeRequest(endpoint: .globalApplicationCommand(applicationId: applicationId, commandId: commandId), + token: token, + requestInfo: .patch(content: nil, extraHeaders: nil), + callback: requestCallback) } /// Default implementation func getApplicationCommands(on guildId: GuildID, callback: @escaping ([DiscordApplicationCommand], HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback([], nil); return } - let requestCallback: DiscordRequestCallback = {data, response, error in + let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .array(commands)? = JSON.jsonFromResponse(data: data, response: response) else { callback([], response) return @@ -102,7 +108,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { options: [DiscordApplicationCommandOption]? = nil, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } - let requestCallback: DiscordRequestCallback = {data, response, error in + let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { callback(nil, response) return @@ -121,7 +127,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { options: [DiscordApplicationCommandOption]? = nil, callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil, nil); return } - let requestCallback: DiscordRequestCallback = {data, response, error in + let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { callback(nil, response) return From 4e37476980d0423228d095b1ffd8e67207b9c471 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 18:01:56 +0100 Subject: [PATCH 086/104] Implement guild command endpoints --- ...DiscordEndpointConsumer+Applications.swift | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift index a4d87cb8e..800518870 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift @@ -79,7 +79,7 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { } rateLimiter.executeRequest(endpoint: .globalApplicationCommand(applicationId: applicationId, commandId: commandId), token: token, - requestInfo: .patch(content: nil, extraHeaders: nil), + requestInfo: .delete(content: nil, extraHeaders: nil), callback: requestCallback) } @@ -116,7 +116,11 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { callback(DiscordApplicationCommand(commandObject: command), response) } - // TODO + let params = CommandParams(name: name, description: description, options: options) + rateLimiter.executeRequest(endpoint: .guildApplicationCommands(applicationId: applicationId, guildId: guildId), + token: token, + requestInfo: .post(content: .json(JSON.encodeJSONData(params) ?? Data()), extraHeaders: nil), + callback: requestCallback) } /// Default implementation @@ -135,7 +139,11 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { callback(DiscordApplicationCommand(commandObject: command), response) } - // TODO + let params = CommandParams(name: name, description: description, options: options) + rateLimiter.executeRequest(endpoint: .guildApplicationCommand(applicationId: applicationId, guildId: guildId, commandId: commandId), + token: token, + requestInfo: .patch(content: .json(JSON.encodeJSONData(params) ?? Data()), extraHeaders: nil), + callback: requestCallback) } /// Default implementation @@ -143,6 +151,12 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { on guildId: GuildID, callback: @escaping (HTTPURLResponse?) -> ()) { guard let applicationId = user?.id else { callback(nil); return } - // TODO + let requestCallback: DiscordRequestCallback = { data, response, error in + callback(response) + } + rateLimiter.executeRequest(endpoint: .guildApplicationCommand(applicationId: applicationId, guildId: guildId, commandId: commandId), + token: token, + requestInfo: .delete(content: nil, extraHeaders: nil), + callback: requestCallback) } } From 22d53d083de660ce4e7044c3e35915bca420b2e2 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 18:14:05 +0100 Subject: [PATCH 087/104] Implement interaction create handler --- Sources/SwiftDiscord/DiscordClient.swift | 4 +++- Sources/SwiftDiscord/DiscordClientDelegate.swift | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/DiscordClient.swift b/Sources/SwiftDiscord/DiscordClient.swift index 58c2e35f7..6785a40e1 100644 --- a/Sources/SwiftDiscord/DiscordClient.swift +++ b/Sources/SwiftDiscord/DiscordClient.swift @@ -873,7 +873,9 @@ open class DiscordClient : DiscordClientSpec, DiscordDispatchEventHandler, Disco /// - parameter with: The data from the event /// open func handleInteractionCreate(with data: [String: Any]) { - // TODO + logger.info("Handling interaction create") + + delegate?.client(self, didCreateInteraction: DiscordInteraction(interactionObject: data)) } /// diff --git a/Sources/SwiftDiscord/DiscordClientDelegate.swift b/Sources/SwiftDiscord/DiscordClientDelegate.swift index 6606b729a..0375b6831 100644 --- a/Sources/SwiftDiscord/DiscordClientDelegate.swift +++ b/Sources/SwiftDiscord/DiscordClientDelegate.swift @@ -315,6 +315,9 @@ public extension DiscordClientDelegate { /// Default. func client(_ client: DiscordClient, didReceivePresenceUpdate presence: DiscordPresence) { } + /// Default. + func client(_ client: DiscordClient, didCreateInteraction interaction: DiscordInteraction) { } + /// Default. func client(_ client: DiscordClient, didReceiveReady readyData: [String: Any]) { } From 09250c5aa53992d55ccc1191819e0ed730d37503 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 18:22:23 +0100 Subject: [PATCH 088/104] Implement REST endpoint for interaction responses --- ...DiscordEndpointConsumer+Interactions.swift | 36 +++++++++++++++++++ .../Rest/DiscordEndpointConsumer.swift | 10 ++++++ 2 files changed, 46 insertions(+) create mode 100644 Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift new file mode 100644 index 000000000..1add5fbb4 --- /dev/null +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift @@ -0,0 +1,36 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +private struct CommandParams: Encodable { + let name: String + let description: String + let options: [DiscordApplicationCommandOption]? +} + +public extension DiscordEndpointConsumer where Self: DiscordUserActor { + /// Default implementation + func createInteractionResponse(for interactionId: InteractionID, + token interactionToken: String, + response: DiscordMessage, + callback: @escaping (HTTPURLResponse?) -> ()) { + let requestCallback: DiscordRequestCallback = { data, response, error in + callback(response) + } + let requestInfo: DiscordEndpoint.EndpointRequest + + switch response.createDataForSending() { + case let .left(data): + requestInfo = .post(content: .json(data), extraHeaders: nil) + case let .right((boundary, body)): + requestInfo = .post(content: .other(type: "multipart/form-data; boundary=\(boundary)", body: body), + extraHeaders: nil) + } + + rateLimiter.executeRequest(endpoint: .interactionsCallback(interactionId: interactionId, interactionToken: interactionToken), + token: token, + requestInfo: requestInfo, + callback: requestCallback) + } +} diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift index 9f27831d5..b6230a909 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift @@ -703,6 +703,16 @@ public protocol DiscordEndpointConsumer { on guildId: GuildID, callback: @escaping (HTTPURLResponse?) -> ()) + /// + /// Creates a response to an interaction from the gateway. + /// + /// - parameter response: The response + /// + func createInteractionResponse(for interactionId: InteractionID, + token: String, + response: DiscordMessage, + callback: @escaping (HTTPURLResponse?) -> ()) + // MARK: Misc /// From 3917ebb7f49c4997f14875eac5500a70c0e439c1 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 18:27:49 +0100 Subject: [PATCH 089/104] Make callbacks optional in command creation methods --- ...DiscordEndpointConsumer+Applications.swift | 44 +++++++++---------- ...DiscordEndpointConsumer+Interactions.swift | 4 +- .../Rest/DiscordEndpointConsumer.swift | 14 +++--- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift index 800518870..9fe6a35f9 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Applications.swift @@ -31,15 +31,15 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { func createApplicationCommand(name: String, description: String, options: [DiscordApplicationCommandOption]? = nil, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { - guard let applicationId = user?.id else { callback(nil, nil); return } + callback: ((DiscordApplicationCommand?, HTTPURLResponse?) -> ())? = nil) { + guard let applicationId = user?.id else { callback?(nil, nil); return } let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { - callback(nil, response) + callback?(nil, response) return } - callback(DiscordApplicationCommand(commandObject: command), response) + callback?(DiscordApplicationCommand(commandObject: command), response) } let params = CommandParams(name: name, description: description, options: options) rateLimiter.executeRequest(endpoint: .globalApplicationCommands(applicationId: applicationId), @@ -53,15 +53,15 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { name: String, description: String, options: [DiscordApplicationCommandOption]? = nil, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { - guard let applicationId = user?.id else { callback(nil, nil); return } + callback: ((DiscordApplicationCommand?, HTTPURLResponse?) -> ())? = nil) { + guard let applicationId = user?.id else { callback?(nil, nil); return } let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { - callback(nil, response) + callback?(nil, response) return } - callback(DiscordApplicationCommand(commandObject: command), response) + callback?(DiscordApplicationCommand(commandObject: command), response) } let params = CommandParams(name: name, description: description, options: options) rateLimiter.executeRequest(endpoint: .globalApplicationCommand(applicationId: applicationId, commandId: commandId), @@ -72,10 +72,10 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation func deleteApplicationCommand(_ commandId: CommandID, - callback: @escaping (HTTPURLResponse?) -> ()) { - guard let applicationId = user?.id else { callback(nil); return } + callback: ((HTTPURLResponse?) -> ())? = nil) { + guard let applicationId = user?.id else { callback?(nil); return } let requestCallback: DiscordRequestCallback = { data, response, error in - callback(response) + callback?(response) } rateLimiter.executeRequest(endpoint: .globalApplicationCommand(applicationId: applicationId, commandId: commandId), token: token, @@ -106,15 +106,15 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { name: String, description: String, options: [DiscordApplicationCommandOption]? = nil, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { - guard let applicationId = user?.id else { callback(nil, nil); return } + callback: ((DiscordApplicationCommand?, HTTPURLResponse?) -> ())? = nil) { + guard let applicationId = user?.id else { callback?(nil, nil); return } let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { - callback(nil, response) + callback?(nil, response) return } - callback(DiscordApplicationCommand(commandObject: command), response) + callback?(DiscordApplicationCommand(commandObject: command), response) } let params = CommandParams(name: name, description: description, options: options) rateLimiter.executeRequest(endpoint: .guildApplicationCommands(applicationId: applicationId, guildId: guildId), @@ -129,15 +129,15 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { name: String, description: String, options: [DiscordApplicationCommandOption]? = nil, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) { - guard let applicationId = user?.id else { callback(nil, nil); return } + callback: ((DiscordApplicationCommand?, HTTPURLResponse?) -> ())? = nil) { + guard let applicationId = user?.id else { callback?(nil, nil); return } let requestCallback: DiscordRequestCallback = { data, response, error in guard case let .object(command)? = JSON.jsonFromResponse(data: data, response: response) else { - callback(nil, response) + callback?(nil, response) return } - callback(DiscordApplicationCommand(commandObject: command), response) + callback?(DiscordApplicationCommand(commandObject: command), response) } let params = CommandParams(name: name, description: description, options: options) rateLimiter.executeRequest(endpoint: .guildApplicationCommand(applicationId: applicationId, guildId: guildId, commandId: commandId), @@ -149,10 +149,10 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation func deleteApplicationCommand(_ commandId: CommandID, on guildId: GuildID, - callback: @escaping (HTTPURLResponse?) -> ()) { - guard let applicationId = user?.id else { callback(nil); return } + callback: ((HTTPURLResponse?) -> ())? = nil) { + guard let applicationId = user?.id else { callback?(nil); return } let requestCallback: DiscordRequestCallback = { data, response, error in - callback(response) + callback?(response) } rateLimiter.executeRequest(endpoint: .guildApplicationCommand(applicationId: applicationId, guildId: guildId, commandId: commandId), token: token, diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift index 1add5fbb4..3d8576556 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift @@ -14,9 +14,9 @@ public extension DiscordEndpointConsumer where Self: DiscordUserActor { func createInteractionResponse(for interactionId: InteractionID, token interactionToken: String, response: DiscordMessage, - callback: @escaping (HTTPURLResponse?) -> ()) { + callback: ((HTTPURLResponse?) -> ())? = nil){ let requestCallback: DiscordRequestCallback = { data, response, error in - callback(response) + callback?(response) } let requestInfo: DiscordEndpoint.EndpointRequest diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift index b6230a909..abe1aca27 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift @@ -642,7 +642,7 @@ public protocol DiscordEndpointConsumer { func createApplicationCommand(name: String, description: String, options: [DiscordApplicationCommandOption]?, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + callback: ((DiscordApplicationCommand?, HTTPURLResponse?) -> ())?) /// /// Edits a global slash-command for a user. @@ -653,7 +653,7 @@ public protocol DiscordEndpointConsumer { name: String, description: String, options: [DiscordApplicationCommandOption]?, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + callback: ((DiscordApplicationCommand?, HTTPURLResponse?) -> ())?) /// /// Deletes a global slash-command for a user. @@ -661,7 +661,7 @@ public protocol DiscordEndpointConsumer { /// - parameter callback: The callback function, taking a command. /// func deleteApplicationCommand(_ commandId: CommandID, - callback: @escaping (HTTPURLResponse?) -> ()) + callback: ((HTTPURLResponse?) -> ())?) /// /// Gets the guild-specific slash-commands of a user. @@ -680,7 +680,7 @@ public protocol DiscordEndpointConsumer { name: String, description: String, options: [DiscordApplicationCommandOption]?, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + callback: ((DiscordApplicationCommand?, HTTPURLResponse?) -> ())?) /// /// Edits a guild-specific slash-command for a user. @@ -692,7 +692,7 @@ public protocol DiscordEndpointConsumer { name: String, description: String, options: [DiscordApplicationCommandOption]?, - callback: @escaping (DiscordApplicationCommand?, HTTPURLResponse?) -> ()) + callback: ((DiscordApplicationCommand?, HTTPURLResponse?) -> ())?) /// /// Deletes a guild-specific slash-command for a user. @@ -701,7 +701,7 @@ public protocol DiscordEndpointConsumer { /// func deleteApplicationCommand(_ commandId: CommandID, on guildId: GuildID, - callback: @escaping (HTTPURLResponse?) -> ()) + callback: ((HTTPURLResponse?) -> ())?) /// /// Creates a response to an interaction from the gateway. @@ -711,7 +711,7 @@ public protocol DiscordEndpointConsumer { func createInteractionResponse(for interactionId: InteractionID, token: String, response: DiscordMessage, - callback: @escaping (HTTPURLResponse?) -> ()) + callback: ((HTTPURLResponse?) -> ())?) // MARK: Misc From 34adfca62bbf2fb8776a18e9094f76e1bff027a6 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 18:39:46 +0100 Subject: [PATCH 090/104] Fix interaction type name typo --- Sources/SwiftDiscord/Interaction/DiscordInteraction.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift index fbe77c6c5..1d1ae701b 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift @@ -45,5 +45,5 @@ public struct DiscordInteraction { public enum DiscordInteractionType: Int { case ping = 1 - case DiscordapplicationCommand = 2 + case applicationCommand = 2 } From 22497a71643cd64d6521091e01d65fb389f27b74 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 18:51:56 +0100 Subject: [PATCH 091/104] Use interaction response structure in endpoint method --- .../DiscordInteractionResponse.swift | 59 +++++++++++++++++++ ...DiscordEndpointConsumer+Interactions.swift | 20 +------ .../Rest/DiscordEndpointConsumer.swift | 2 +- 3 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 Sources/SwiftDiscord/Interaction/DiscordInteractionResponse.swift diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteractionResponse.swift b/Sources/SwiftDiscord/Interaction/DiscordInteractionResponse.swift new file mode 100644 index 000000000..727363ecd --- /dev/null +++ b/Sources/SwiftDiscord/Interaction/DiscordInteractionResponse.swift @@ -0,0 +1,59 @@ +import Foundation + +public struct DiscordInteractionResponse: Encodable { + // MARK: Properties + + /// The type of response + public let type: DiscordInteractionResponseType + + /// An optional response message + public let data: DiscordInteractionApplicationCommandCallbackData? +} + +public enum DiscordInteractionResponseType: Int, Encodable { + /// Ack a ping + case pong = 1 + + /// Ack a command without sending a message, eating the user's input + case acknowledge = 2 + + /// Respond with a message, eating the user's input + case channelMessage = 3 + + /// Respond with a message, showing the user's input + case channelMessageWithSource = 4 + + /// Ack a command without sending a message, showing the user's input + case ackWithSource = 5 + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } +} + +public struct DiscordInteractionApplicationCommandCallbackData: Encodable { + public enum CodingKeys: String, CodingKey { + case tts + case content + case embeds + case allowedMentions = "allowed_mentions" + } + + public let tts: Bool? + public let content: String? + public let embeds: [DiscordEmbed]? + public let allowedMentions: DiscordAllowedMentions? + + public init( + tts: Bool? = nil, + content: String? = nil, + embeds: [DiscordEmbed]? = nil, + allowedMentions: DiscordAllowedMentions? = nil + ) { + self.tts = tts + self.content = content + self.embeds = embeds + self.allowedMentions = allowedMentions + } +} diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift index 3d8576556..ffb72160b 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer+Interactions.swift @@ -3,34 +3,18 @@ import Foundation import FoundationNetworking #endif -private struct CommandParams: Encodable { - let name: String - let description: String - let options: [DiscordApplicationCommandOption]? -} - public extension DiscordEndpointConsumer where Self: DiscordUserActor { /// Default implementation func createInteractionResponse(for interactionId: InteractionID, token interactionToken: String, - response: DiscordMessage, + response: DiscordInteractionResponse, callback: ((HTTPURLResponse?) -> ())? = nil){ let requestCallback: DiscordRequestCallback = { data, response, error in callback?(response) } - let requestInfo: DiscordEndpoint.EndpointRequest - - switch response.createDataForSending() { - case let .left(data): - requestInfo = .post(content: .json(data), extraHeaders: nil) - case let .right((boundary, body)): - requestInfo = .post(content: .other(type: "multipart/form-data; boundary=\(boundary)", body: body), - extraHeaders: nil) - } - rateLimiter.executeRequest(endpoint: .interactionsCallback(interactionId: interactionId, interactionToken: interactionToken), token: token, - requestInfo: requestInfo, + requestInfo: .post(content: .json(JSON.encodeJSONData(response) ?? Data()), extraHeaders: nil), callback: requestCallback) } } diff --git a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift index abe1aca27..7ddbb2be3 100644 --- a/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift +++ b/Sources/SwiftDiscord/Rest/DiscordEndpointConsumer.swift @@ -710,7 +710,7 @@ public protocol DiscordEndpointConsumer { /// func createInteractionResponse(for interactionId: InteractionID, token: String, - response: DiscordMessage, + response: DiscordInteractionResponse, callback: ((HTTPURLResponse?) -> ())?) // MARK: Misc From 8ef8229c03198503664ee305b49b014799f5eef1 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 18:53:18 +0100 Subject: [PATCH 092/104] Add public initializer for interaction response structure --- .../Interaction/DiscordInteractionResponse.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteractionResponse.swift b/Sources/SwiftDiscord/Interaction/DiscordInteractionResponse.swift index 727363ecd..fc4409dbd 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordInteractionResponse.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordInteractionResponse.swift @@ -8,6 +8,14 @@ public struct DiscordInteractionResponse: Encodable { /// An optional response message public let data: DiscordInteractionApplicationCommandCallbackData? + + public init( + type: DiscordInteractionResponseType, + data: DiscordInteractionApplicationCommandCallbackData? = nil + ) { + self.type = type + self.data = data + } } public enum DiscordInteractionResponseType: Int, Encodable { From 9c953acdb4643069b53bb4dd49e276d2046a186a Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 20:19:59 +0100 Subject: [PATCH 093/104] Add public initializers for command options --- .../DiscordApplicationCommand.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift index cdc77b224..1da7cc823 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift @@ -75,6 +75,24 @@ public struct DiscordApplicationCommandOption: Encodable { /// nested options will be the parameters public let options: [DiscordApplicationCommandOption]? + public init( + type: DiscordApplicationCommandOptionType, + name: String, + description: String, + isDefault: Bool? = nil, + isRequired: Bool? = nil, + choices: [DiscordApplicationCommandOptionChoice]? = nil, + options: [DiscordApplicationCommandOption]? = nil + ) { + self.type = type + self.name = name + self.description = description + self.isDefault = isDefault + self.isRequired = isRequired + self.choices = choices + self.options = options + } + init(optionObject: [String: Any]) { type = (optionObject["type"] as? Int).flatMap(DiscordApplicationCommandOptionType.init(rawValue:)) name = (optionObject["name"] as? String) ?? "" @@ -93,6 +111,11 @@ public struct DiscordApplicationCommandOptionChoice: Encodable { /// Value of the choice public let value: DiscordApplicationCommandOptionChoiceValue? + public init(name: String, value: DiscordApplicationCommandOptionChoiceValue? = nil) { + self.name = name + self.value = value + } + init?(choiceObject: [String: Any]) { name = (choiceObject["name"] as? String) ?? "" let rawValue = choiceObject["value"] From 9d6461c7e27e02d822f827856a92cedee2fb0791 Mon Sep 17 00:00:00 2001 From: fwcd Date: Tue, 22 Dec 2020 23:35:58 +0100 Subject: [PATCH 094/104] Add 'APPLICATION_COMMAND_CREATE' to the gateway events --- Sources/SwiftDiscord/Gateway/DiscordEvent.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SwiftDiscord/Gateway/DiscordEvent.swift b/Sources/SwiftDiscord/Gateway/DiscordEvent.swift index 4882e03b9..4776892c4 100644 --- a/Sources/SwiftDiscord/Gateway/DiscordEvent.swift +++ b/Sources/SwiftDiscord/Gateway/DiscordEvent.swift @@ -128,6 +128,10 @@ public enum DiscordDispatchEvent : String { /// Webhooks Update (Not handled) case webhooksUpdate = "WEBHOOKS_UPDATE" + // Applications + + case applicationCommandCreate = "APPLICATION_COMMAND_CREATE" + // Interactions /// Interaction Create (Handled) From 06cafaac4f5f540695032de72773efbb063597a8 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 13 May 2021 17:01:57 +0200 Subject: [PATCH 095/104] Replace protocol class bounds with AnyObject Using the `class` keyword for protocol inheritance is deprecated in Swift 5.4. --- Sources/SwiftDiscord/DiscordClientDelegate.swift | 2 +- Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftDiscord/DiscordClientDelegate.swift b/Sources/SwiftDiscord/DiscordClientDelegate.swift index 0375b6831..ac8f1202f 100644 --- a/Sources/SwiftDiscord/DiscordClientDelegate.swift +++ b/Sources/SwiftDiscord/DiscordClientDelegate.swift @@ -19,7 +19,7 @@ /// Declares that a type will be a delegate for a `DiscordClient`. After the client handles any events, /// the corresponding delegate method will be called. /// -public protocol DiscordClientDelegate : class { +public protocol DiscordClientDelegate : AnyObject { // MARK: Methods /// diff --git a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift index 81ec42e74..40891da3c 100644 --- a/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift +++ b/Sources/SwiftDiscord/Voice/DiscordVoiceDataSource.swift @@ -23,7 +23,7 @@ import Logging fileprivate let logger = Logger(label: "DiscordVoiceDataSource") /// Specifies that a type will be a data source for a VoiceEngine. -public protocol DiscordVoiceDataSource : class { +public protocol DiscordVoiceDataSource : AnyObject { // MARK: Properties /// The size of a frame in samples per channel. Needed to calculate the maximum size of a frame. From 47b51251006b7fd2fa885bda847ca541b2b5d3db Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 31 May 2021 01:24:22 +0200 Subject: [PATCH 096/104] Add new message components API --- .../SwiftDiscord/Channel/DiscordMessage.swift | 99 ++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index 2bac52755..52e2e0d44 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -119,6 +119,9 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { /// A referenced message in an outgoing message. public let messageReference: DiscordMessageReference? + /// Interactive components (e.g. buttons) in the message + public let components: [DiscordMessageComponent]? + /// The type of this message. public let type: MessageType @@ -161,6 +164,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { allowedMentions = nil referencedMessage = messageObject.get("referenced_message", as: [String: Any].self) messageReference = nil + components = nil files = [] type = MessageType(rawValue: messageObject.get("type", or: 0)) ?? .default self.client = client @@ -174,7 +178,15 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { /// - parameter files: The files to send with this message. /// - parameter tts: Whether this message should be text-to-speach. /// - public init(content: String, embed: DiscordEmbed? = nil, files: [DiscordFileUpload] = [], tts: Bool = false, allowedMentions: DiscordAllowedMentions? = nil, messageReference: DiscordMessageReference? = nil) { + public init( + content: String, + embed: DiscordEmbed? = nil, + files: [DiscordFileUpload] = [], + tts: Bool = false, + allowedMentions: DiscordAllowedMentions? = nil, + messageReference: DiscordMessageReference? = nil, + components: [DiscordMessageComponent] = [] + ) { self.content = content if let embed = embed { self.embeds = [embed] @@ -187,6 +199,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { self.tts = tts self.allowedMentions = allowedMentions self.messageReference = messageReference + self.components = components self.referencedMessage = nil self.attachments = [] self.author = DiscordUser(userObject: [:]) @@ -910,6 +923,90 @@ public struct DiscordMessageReference : Encodable { } } +/// An interactive part of a message. +public struct DiscordMessageComponent : Encodable { + public enum CodingKeys : String, CodingKey { + case type + case style + case label + case emoji + case customId = "custom_id" + case url + case disabled + } + + /// The type of the component. + public let type: DiscordMessageComponentType + /// One of a few button styles. Only valid for buttons. + public let style: DiscordMessageComponentButtonStyle? + /// Label that appears on a button. Only valid for buttons. + public let label: String? + /// Emoji that appears on the button. Only valid for buttons. + public let emoji: DiscordMessageComponentEmoji? + /// A developer-defined id for the button, max 100 chars. Only valid for buttons. + public let customId: String? + /// A URL for link-style buttons. Only valid for buttons. + public let url: URL? + /// Whether the button is disabled. False by default. Only valid for buttons. + public let disabled: Bool? + + public init( + type: DiscordMessageComponentType, + style: DiscordMessageComponentButtonStyle? = nil, + label: String? = nil, + emoji: DiscordMessageComponentEmoji? = nil, + customId: String? = nil, + url: URL? = nil, + disabled: Bool? = nil + ) { + self.type = type + self.style = style + self.label = label + self.emoji = emoji + self.customId = customId + self.url = url + self.disabled = disabled + } +} + +public struct DiscordMessageComponentType : RawRepresentable, Encodable { + public let rawValue: Int + + public static let actionRow = DiscordMessageComponentType(rawValue: 1) + public static let button = DiscordMessageComponentType(rawValue: 2) + + public init(rawValue: Int) { + self.rawValue = rawValue + } +} + +/// A partial emoji for use in message components. +public struct DiscordMessageComponentEmoji : Encodable { + public let id: EmojiID? + public let name: String? + public let animated: Bool + + public init(id: EmojiID? = nil, name: String? = nil, animated: Bool = false) { + self.id = id + self.name = name + self.animated = animated + } +} + +public struct DiscordMessageComponentButtonStyle : RawRepresentable, Encodable { + public let rawValue: Int + + public static let primary = DiscordMessageComponentButtonStyle(rawValue: 1) + public static let secondary = DiscordMessageComponentButtonStyle(rawValue: 2) + public static let success = DiscordMessageComponentButtonStyle(rawValue: 3) + public static let danger = DiscordMessageComponentButtonStyle(rawValue: 4) + public static let link = DiscordMessageComponentButtonStyle(rawValue: 5) + + public init(rawValue: Int) { + self.rawValue = rawValue + } +} + public enum DiscordMessageStickerFormatType: Int { case png = 1 case apng = 2 From 29e94c89f0324243aace6b96100eb31b45dbc7ca Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 31 May 2021 01:28:57 +0200 Subject: [PATCH 097/104] Add action rows and some helper functions --- .../SwiftDiscord/Channel/DiscordMessage.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index 52e2e0d44..376bbc282 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -937,6 +937,8 @@ public struct DiscordMessageComponent : Encodable { /// The type of the component. public let type: DiscordMessageComponentType + /// Sub-components. Only valid for action rows. + public let components: [DiscordMessageComponent]? /// One of a few button styles. Only valid for buttons. public let style: DiscordMessageComponentButtonStyle? /// Label that appears on a button. Only valid for buttons. @@ -952,6 +954,7 @@ public struct DiscordMessageComponent : Encodable { public init( type: DiscordMessageComponentType, + components: [DiscordMessageComponent]? = nil, style: DiscordMessageComponentButtonStyle? = nil, label: String? = nil, emoji: DiscordMessageComponentEmoji? = nil, @@ -960,6 +963,7 @@ public struct DiscordMessageComponent : Encodable { disabled: Bool? = nil ) { self.type = type + self.components = components self.style = style self.label = label self.emoji = emoji @@ -967,6 +971,34 @@ public struct DiscordMessageComponent : Encodable { self.url = url self.disabled = disabled } + + /// Creates a new button component. + public static func button( + style: DiscordMessageComponentButtonStyle? = nil, + label: String? = nil, + emoji: DiscordMessageComponentEmoji? = nil, + customId: String? = nil, + url: URL? = nil, + disabled: Bool? = nil + ) -> DiscordMessageComponent { + DiscordMessageComponent( + type: .button, + style: style, + label: label, + emoji: emoji, + customId: customId, + url: url, + disabled: disabled + ) + } + + /// Creates a new action row component. Cannot contain other action rows. + public static func actionRow(components: [DiscordMessageComponent]) -> DiscordMessageComponent { + DiscordMessageComponent( + type: .actionRow, + components: components + ) + } } public struct DiscordMessageComponentType : RawRepresentable, Encodable { From be1f35f188e57b4dd0da4d8181c7b9fa8fa4aa6e Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 31 May 2021 01:31:48 +0200 Subject: [PATCH 098/104] Add DiscordInteractionType.messageComponent Also refactor the type into a RawRepresentable structure. --- .../Interaction/DiscordInteraction.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift index 1d1ae701b..9930c8b12 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift @@ -43,7 +43,14 @@ public struct DiscordInteraction { } } -public enum DiscordInteractionType: Int { - case ping = 1 - case applicationCommand = 2 +public struct DiscordInteractionType: RawRepresentable { + public var rawValue: Int + + public static let ping = DiscordInteractionType(rawValue: 1) + public static let applicationCommand = DiscordInteractionType(rawValue: 2) + public static let messageComponent = DiscordInteractionType(rawValue: 3) + + public init(rawValue: Int) { + self.rawValue = rawValue + } } From f4b3b3f38d3c21bdf84ebf743d06215e28d6c213 Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 31 May 2021 01:38:05 +0200 Subject: [PATCH 099/104] Parse message from interaction response --- Sources/SwiftDiscord/Interaction/DiscordInteraction.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift index 9930c8b12..e3e5f15dd 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift @@ -15,6 +15,9 @@ public struct DiscordInteraction { /// types, but optional for future-proofing. public let data: DiscordApplicationCommandInteractionData? + /// The message a user interacted with, e.g. when pressing a button. + public let message: DiscordMessage? + /// Guild it was sent from public let guildId: GuildID @@ -34,6 +37,7 @@ public struct DiscordInteraction { id = Snowflake(interactionObject["id"] as? String) ?? 0 type = (interactionObject["type"] as? Int).flatMap(DiscordInteractionType.init(rawValue:)) data = (interactionObject["data"] as? [String: Any]).map(DiscordApplicationCommandInteractionData.init(dataObject:)) + message = (interactionObject["message"] as? [String: Any]).map { DiscordMessage(messageObject: $0, client: nil) } let guildId = Snowflake(interactionObject["guild_id"] as? String) ?? 0 self.guildId = guildId channelId = Snowflake(interactionObject["channel_id"] as? String) ?? 0 From 38289e3b6c46442d839893ff865dfe6397f75510 Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 31 May 2021 01:44:42 +0200 Subject: [PATCH 100/104] Update components doc comment --- Sources/SwiftDiscord/Channel/DiscordMessage.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index 376bbc282..744ee4039 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -119,7 +119,8 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { /// A referenced message in an outgoing message. public let messageReference: DiscordMessageReference? - /// Interactive components (e.g. buttons) in the message + /// Interactive components in the message. This top-level array should only + /// contain action rows (which can then e.g. contain buttons). public let components: [DiscordMessageComponent]? /// The type of this message. From 8de1887a90f1e1ebe06c747a363e7e51b03a263b Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 31 May 2021 01:47:38 +0200 Subject: [PATCH 101/104] Add customId to DiscordInteraction --- Sources/SwiftDiscord/Interaction/DiscordInteraction.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift index e3e5f15dd..73f372e77 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift @@ -18,6 +18,9 @@ public struct DiscordInteraction { /// The message a user interacted with, e.g. when pressing a button. public let message: DiscordMessage? + /// A custom (developer-defined) id attached to e.g. a button interaction. + public let customId: String? + /// Guild it was sent from public let guildId: GuildID @@ -38,6 +41,7 @@ public struct DiscordInteraction { type = (interactionObject["type"] as? Int).flatMap(DiscordInteractionType.init(rawValue:)) data = (interactionObject["data"] as? [String: Any]).map(DiscordApplicationCommandInteractionData.init(dataObject:)) message = (interactionObject["message"] as? [String: Any]).map { DiscordMessage(messageObject: $0, client: nil) } + customId = interactionObject["custom_id"] as? String let guildId = Snowflake(interactionObject["guild_id"] as? String) ?? 0 self.guildId = guildId channelId = Snowflake(interactionObject["channel_id"] as? String) ?? 0 From 2fb1e2d7d53d3739b24e01a7e72a5540ada1fb69 Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 31 May 2021 02:03:22 +0200 Subject: [PATCH 102/104] Add missing components coding key --- Sources/SwiftDiscord/Channel/DiscordMessage.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index 744ee4039..93040595c 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -27,6 +27,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { case embed case allowedMentions = "allowed_mentions" case messageReference = "message_reference" + case components } let content: String @@ -34,6 +35,7 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { let embed: DiscordEmbed? let allowedMentions: DiscordAllowedMentions? let messageReference: DiscordMessageReference? + let components: [DiscordMessageComponent]? } // MARK: Typealiases @@ -248,7 +250,14 @@ public struct DiscordMessage : DiscordClientHolder, ExpressibleByStringLiteral { // MARK: Methods func createDataForSending() -> Either { - let fields = FieldsList(content: content, tts: tts, embed: embeds.first, allowedMentions: allowedMentions, messageReference: messageReference) + let fields = FieldsList( + content: content, + tts: tts, + embed: embeds.first, + allowedMentions: allowedMentions, + messageReference: messageReference, + components: components + ) let fieldsData = JSON.encodeJSONData(fields) ?? Data() if files.count > 0 { return .right(createMultipartBody(encodedJSON: fieldsData, files: files)) @@ -928,6 +937,7 @@ public struct DiscordMessageReference : Encodable { public struct DiscordMessageComponent : Encodable { public enum CodingKeys : String, CodingKey { case type + case components case style case label case emoji From a6f8f279636c341e49123c22a3a24f2da75a3c88 Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 31 May 2021 02:04:29 +0200 Subject: [PATCH 103/104] Move customId to application command interaction data --- .../SwiftDiscord/Interaction/DiscordApplicationCommand.swift | 4 ++++ Sources/SwiftDiscord/Interaction/DiscordInteraction.swift | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift index 1da7cc823..2a988fc6d 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordApplicationCommand.swift @@ -167,12 +167,16 @@ public struct DiscordApplicationCommandInteractionData { /// The name of the invoked command public let name: String + /// A custom (developer-defined) id attached to e.g. a button interaction. + public let customId: String? + /// The params + values by the user public let options: [DiscordApplicationCommandInteractionDataOption] init(dataObject: [String: Any]) { id = Snowflake(dataObject["id"] as? String) ?? 0 name = dataObject.get("name", as: String.self) ?? "" + customId = dataObject["custom_id"] as? String options = (dataObject["options"] as? [[String: Any]])?.map(DiscordApplicationCommandInteractionDataOption.init(optionObject:)).compactMap { $0 } ?? [] } } diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift index 73f372e77..e3e5f15dd 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift @@ -18,9 +18,6 @@ public struct DiscordInteraction { /// The message a user interacted with, e.g. when pressing a button. public let message: DiscordMessage? - /// A custom (developer-defined) id attached to e.g. a button interaction. - public let customId: String? - /// Guild it was sent from public let guildId: GuildID @@ -41,7 +38,6 @@ public struct DiscordInteraction { type = (interactionObject["type"] as? Int).flatMap(DiscordInteractionType.init(rawValue:)) data = (interactionObject["data"] as? [String: Any]).map(DiscordApplicationCommandInteractionData.init(dataObject:)) message = (interactionObject["message"] as? [String: Any]).map { DiscordMessage(messageObject: $0, client: nil) } - customId = interactionObject["custom_id"] as? String let guildId = Snowflake(interactionObject["guild_id"] as? String) ?? 0 self.guildId = guildId channelId = Snowflake(interactionObject["channel_id"] as? String) ?? 0 From 062551553f91b3f4ab73b0a6e0d24071cc28d273 Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 31 May 2021 02:25:39 +0200 Subject: [PATCH 104/104] Make RawRepresentables Hashable --- Sources/SwiftDiscord/Channel/DiscordMessage.swift | 4 ++-- Sources/SwiftDiscord/Interaction/DiscordInteraction.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftDiscord/Channel/DiscordMessage.swift b/Sources/SwiftDiscord/Channel/DiscordMessage.swift index 93040595c..73c078acd 100644 --- a/Sources/SwiftDiscord/Channel/DiscordMessage.swift +++ b/Sources/SwiftDiscord/Channel/DiscordMessage.swift @@ -1012,7 +1012,7 @@ public struct DiscordMessageComponent : Encodable { } } -public struct DiscordMessageComponentType : RawRepresentable, Encodable { +public struct DiscordMessageComponentType : RawRepresentable, Hashable, Encodable { public let rawValue: Int public static let actionRow = DiscordMessageComponentType(rawValue: 1) @@ -1036,7 +1036,7 @@ public struct DiscordMessageComponentEmoji : Encodable { } } -public struct DiscordMessageComponentButtonStyle : RawRepresentable, Encodable { +public struct DiscordMessageComponentButtonStyle : RawRepresentable, Hashable, Encodable { public let rawValue: Int public static let primary = DiscordMessageComponentButtonStyle(rawValue: 1) diff --git a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift index e3e5f15dd..7c7015891 100644 --- a/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift +++ b/Sources/SwiftDiscord/Interaction/DiscordInteraction.swift @@ -47,7 +47,7 @@ public struct DiscordInteraction { } } -public struct DiscordInteractionType: RawRepresentable { +public struct DiscordInteractionType: RawRepresentable, Hashable { public var rawValue: Int public static let ping = DiscordInteractionType(rawValue: 1)