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) { print(gatewayURL.absoluteURL) print(gatewayURL.appending(queryItems: [.init(name: "v", value: "10"), .init(name: "encoding", value: "json")])) 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) //dump(try await getMessage()) //print("got here") _ = 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 { let wsMessage = try! await ws.receive() print(wsMessage) guard case .string(let str) = wsMessage else { throw GatewayError.invalidMessage } let json = JSONDecoder() let gwMessage = try json.decode(GatewayMessage.self, from: Data(str.utf8)) sequenceNum = gwMessage.s ?? sequenceNum return gwMessage } private func sendHeartbeat() async throws { let hbMessage = "{\"op\":1,\"d\":\(sequenceNum == nil ? "null" : String(sequenceNum!))}" print(hbMessage) try await ws.send(.string(hbMessage)) } var events: AsyncStream { AsyncStream { [self] in guard await open else { return nil } do { let event = try await getMessage() if event.op == 1 { try await sendHeartbeat() } return event } catch { print("Error listening to gateway: \(error)") return nil } } } } public enum GatewayError: Error { case invalidMessage case invalidOpcode case mismatchedOpcode }