DiscordKit: can now get to ready event

This commit is contained in:
2026-03-17 16:37:31 -04:00
parent 29a2b0370b
commit 7506a06d7f
6 changed files with 139 additions and 39 deletions

View File

@@ -3,15 +3,23 @@ import Foundation
import FoundationNetworking
#endif
public struct Bot {
public actor Bot {
let client: ApiClient
let gateway: GatewayClient
let intents: Intents
public init(token: String) async throws {
public init(token: String, intents: Intents) async throws {
client = ApiClient(token: token)
self.intents = intents
let gatewayURL = try await client.getGatewayURL()
gateway = GatewayClient(gatewayURL: gatewayURL, token: token)
}
let gateway = GatewayClient(gatewayURL: gatewayURL)
try await gateway.openConnection()
public func connect() async throws {
try await gateway.openConnection(intents: intents)
}
public var events: AsyncStream<GatewayMessage> {
get async { await gateway.events }
}
}

View File

@@ -3,25 +3,60 @@ import Foundation
import FoundationNetworking
#endif
class GatewayClient {
let ws: URLSessionWebSocketTask
actor GatewayClient {
private let ws: URLSessionWebSocketTask
private let token: String
private(set) var open = false
var sequenceNum: Int? = nil
init(gatewayURL: URL) {
init(gatewayURL: URL, token: String) {
ws = URLSession.shared.webSocketTask(with: gatewayURL.appending(component: "?v=10&encoding=json"))
self.token = token
}
func openConnection() async throws {
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)
Timer.scheduledTimer(withTimeInterval: (helloMessage.heartbeat_interval / 1000.0), repeats: true) {
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()
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))
@@ -29,6 +64,26 @@ class GatewayClient {
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 {

View File

@@ -3,24 +3,55 @@ import Foundation
import FoundationNetworking
#endif
public struct GetGatewayResponse: Codable {
/// https://discord.com/developers/docs/topics/gateway#gateway-intents
public struct Intents: OptionSet, Sendable {
public var rawValue: UInt32
public static let guilds = Intents(rawValue: 1 << 0)
public static let guildMembers = Intents(rawValue: 1 << 1)
public static let guildModeration = Intents(rawValue: 1 << 2)
public static let guildEmojisAndStickers = Intents(rawValue: 1 << 3)
public static let guildIntegrations = Intents(rawValue: 1 << 4)
public static let guildWebhooks = Intents(rawValue: 1 << 5)
public static let guildInvites = Intents(rawValue: 1 << 6)
public static let guildVoiceStates = Intents(rawValue: 1 << 7)
public static let guildPresences = Intents(rawValue: 1 << 8)
public static let guildMessages = Intents(rawValue: 1 << 9)
public static let guildMessageReactions = Intents(rawValue: 1 << 10)
public static let guildMessageTyping = Intents(rawValue: 1 << 11)
public static let directMessages = Intents(rawValue: 1 << 12)
public static let directMessageReactions = Intents(rawValue: 1 << 13)
public static let directMessageTyping = Intents(rawValue: 1 << 14)
public static let messageContent = Intents(rawValue: 1 << 15)
public static let guildScheduledEvents = Intents(rawValue: 1 << 16)
public static let autoModerationConfiguration = Intents(rawValue: 1 << 20)
public static let autoModerationExecution = Intents(rawValue: 1 << 21)
public static let guildMessagePolls = Intents(rawValue: 1 << 24)
public static let directMessagePolls = Intents(rawValue: 1 << 25)
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
}
public struct GetGatewayResponse: Codable, Sendable {
let url: URL
let shards: Int
let session_start_limit: SessionStartLimit
}
public struct SessionStartLimit: Codable {
public struct SessionStartLimit: Codable, Sendable {
let total: Int
let remaining: Int
let reset_after: Int
let max_concurrency: Int
}
public enum GatewayPayload: Decodable {
public enum GatewayPayload: Decodable, Sendable {
case hello(HelloPayload)
}
public struct GatewayMessage: Decodable {
public struct GatewayMessage: Decodable, Sendable {
let op: Int
let d: GatewayPayload?
let s: Int?
@@ -47,8 +78,16 @@ public struct GatewayMessage: Decodable {
break
}
}
public init(op: Int, d: GatewayPayload?, s: Int?, t: String?) {
self.op = op
self.d = d
self.s = s
self.t = t
}
}
public struct HelloPayload: Codable {
public struct HelloPayload: Codable, Sendable {
let heartbeat_interval: Int
}