Files
zunda-bot/Sources/DiscordKit/GatewayClient.swift

95 lines
2.9 KiB
Swift

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)
//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<GatewayMessage> {
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
}