import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif actor GatewayClient { private let ws: URLSessionWebSocketTask private let token: String private(set) var open = false var sequenceNum: Int? = nil init(gatewayURL: URL, token: String) { let queryItems = [URLQueryItem(name: "v", value: "10"), URLQueryItem(name: "encoding", value: "json")] ws = URLSession.shared.webSocketTask(with: gatewayURL.appending(queryItems: queryItems)) self.token = token } func openConnection(intents: Intents) async throws { ws.resume() open = true guard case .hello(let helloMessage) = try await getMessage().d else { throw GatewayError.mismatchedOpcode } dump(helloMessage) let heartbeatTask = Task() { try await Task.sleep(for: .milliseconds(Int.random(in: 0...helloMessage.heartbeat_interval))) try await sendHeartbeat() while !Task.isCancelled { try await Task.sleep(for: .milliseconds(helloMessage.heartbeat_interval)) try await sendHeartbeat() } } try await sendIdentify(intents: intents) _ = await heartbeatTask.result } func sendIdentify(intents: Intents) async throws { let payload = """ { "op": 2, "d": { "token": "\(token)", "intents": \(intents.rawValue), "properties": { "os": "linux", "browser": "discordkit", "device": "discordkit" } } } """ // Im lazy try await ws.send(.string(payload)) } func getMessage() async throws -> GatewayMessage { var strBuffer = "" var gwMessage: GatewayMessage? = nil let json = JSONDecoder() while gwMessage == nil { let wsMessage = try await ws.receive() guard case .string(let str) = wsMessage else { throw GatewayError.invalidMessage } strBuffer.append(str) do { gwMessage = try json.decode(GatewayMessage.self, from: Data(strBuffer.utf8)) } catch DecodingError.dataCorrupted { continue } } guard let gwMessage = gwMessage else { throw GatewayError.invalidMessage } sequenceNum = gwMessage.s ?? sequenceNum return gwMessage } private func sendHeartbeat() async throws { let hbMessage = "{\"op\":1,\"d\":\(sequenceNum == nil ? "null" : String(sequenceNum!))}" try await ws.send(.string(hbMessage)) } var events: AsyncStream { AsyncStream { [self] in var event: GatewayMessage? = nil while event == nil { do { while await !open { try await Task.sleep(for: .seconds(1)) } event = try await getMessage() if event!.op == 1 { try await sendHeartbeat() } } catch { print("Error listening to gateway: \(error)") } } return event! } } } public enum GatewayError: Error { case invalidMessage case invalidOpcode case mismatchedOpcode }