107 lines
3.3 KiB
Swift
107 lines
3.3 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 {
|
|
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<GatewayMessage> {
|
|
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
|
|
}
|