DiscordKit: can now get to ready event
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "219f2158581681a4b5edbd171ed8aebcd120092f145806d33067fb0cfbb93848",
|
"originHash" : "5244b11ed61f26bb7868e246b3fa58e4ccca64cf7a3b26777a9a1447030fa349",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "async-http-client",
|
"identity" : "async-http-client",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ let package = Package(
|
|||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/DiscordBM/DiscordBM.git", branch: "main"),
|
.package(url: "https://github.com/DiscordBM/DiscordBM.git", branch: "main"),
|
||||||
.package(url: "https://github.com/thebarndog/swift-dotenv.git", from: "2.1.0"),
|
.package(url: "https://github.com/thebarndog/swift-dotenv.git", from: "2.1.0"),
|
||||||
.package(url: "https://github.com/CoreOffice/XMLCoder.git", from: "0.18.0")
|
.package(url: "https://github.com/CoreOffice/XMLCoder.git", from: "0.18.0"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||||
@@ -24,6 +24,8 @@ let package = Package(
|
|||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "DiscordKit",
|
name: "DiscordKit",
|
||||||
|
dependencies: [
|
||||||
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,15 +3,23 @@ import Foundation
|
|||||||
import FoundationNetworking
|
import FoundationNetworking
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public struct Bot {
|
public actor Bot {
|
||||||
let client: ApiClient
|
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)
|
client = ApiClient(token: token)
|
||||||
|
self.intents = intents
|
||||||
let gatewayURL = try await client.getGatewayURL()
|
let gatewayURL = try await client.getGatewayURL()
|
||||||
|
gateway = GatewayClient(gatewayURL: gatewayURL, token: token)
|
||||||
|
}
|
||||||
|
|
||||||
let gateway = GatewayClient(gatewayURL: gatewayURL)
|
public func connect() async throws {
|
||||||
try await gateway.openConnection()
|
try await gateway.openConnection(intents: intents)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var events: AsyncStream<GatewayMessage> {
|
||||||
|
get async { await gateway.events }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,60 @@ import Foundation
|
|||||||
import FoundationNetworking
|
import FoundationNetworking
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class GatewayClient {
|
actor GatewayClient {
|
||||||
let ws: URLSessionWebSocketTask
|
private let ws: URLSessionWebSocketTask
|
||||||
|
private let token: String
|
||||||
|
private(set) var open = false
|
||||||
var sequenceNum: Int? = nil
|
var sequenceNum: Int? = nil
|
||||||
|
|
||||||
init(gatewayURL: URL) {
|
init(gatewayURL: URL, token: String) {
|
||||||
ws = URLSession.shared.webSocketTask(with: gatewayURL.appending(component: "?v=10&encoding=json"))
|
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()
|
ws.resume()
|
||||||
|
open = true
|
||||||
guard case .hello(let helloMessage) = try await getMessage().d else { throw GatewayError.mismatchedOpcode }
|
guard case .hello(let helloMessage) = try await getMessage().d else { throw GatewayError.mismatchedOpcode }
|
||||||
dump(helloMessage)
|
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 {
|
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 }
|
guard case .string(let str) = wsMessage else { throw GatewayError.invalidMessage }
|
||||||
let json = JSONDecoder()
|
let json = JSONDecoder()
|
||||||
let gwMessage = try json.decode(GatewayMessage.self, from: Data(str.utf8))
|
let gwMessage = try json.decode(GatewayMessage.self, from: Data(str.utf8))
|
||||||
@@ -29,6 +64,26 @@ class GatewayClient {
|
|||||||
return gwMessage
|
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 {
|
public enum GatewayError: Error {
|
||||||
|
|||||||
@@ -3,24 +3,55 @@ import Foundation
|
|||||||
import FoundationNetworking
|
import FoundationNetworking
|
||||||
#endif
|
#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 url: URL
|
||||||
let shards: Int
|
let shards: Int
|
||||||
let session_start_limit: SessionStartLimit
|
let session_start_limit: SessionStartLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SessionStartLimit: Codable {
|
public struct SessionStartLimit: Codable, Sendable {
|
||||||
let total: Int
|
let total: Int
|
||||||
let remaining: Int
|
let remaining: Int
|
||||||
let reset_after: Int
|
let reset_after: Int
|
||||||
let max_concurrency: Int
|
let max_concurrency: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum GatewayPayload: Decodable {
|
public enum GatewayPayload: Decodable, Sendable {
|
||||||
case hello(HelloPayload)
|
case hello(HelloPayload)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct GatewayMessage: Decodable {
|
public struct GatewayMessage: Decodable, Sendable {
|
||||||
let op: Int
|
let op: Int
|
||||||
let d: GatewayPayload?
|
let d: GatewayPayload?
|
||||||
let s: Int?
|
let s: Int?
|
||||||
@@ -47,8 +78,16 @@ public struct GatewayMessage: Decodable {
|
|||||||
break
|
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
|
let heartbeat_interval: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,36 +7,32 @@ struct Zundamon {
|
|||||||
//nonisolated(unsafe) static private(set) var ownID: UserSnowflake? = nil
|
//nonisolated(unsafe) static private(set) var ownID: UserSnowflake? = nil
|
||||||
|
|
||||||
static func main() async throws {
|
static func main() async throws {
|
||||||
let tmp = Result { try Dotenv.configure() }
|
if case .failure = Result(catching: { try Dotenv.configure() }) {
|
||||||
switch (tmp) {
|
|
||||||
case .success:
|
|
||||||
break
|
|
||||||
case .failure:
|
|
||||||
print("Failed to load .env file")
|
print("Failed to load .env file")
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let token = ProcessInfo.processInfo.environment["DISCORD_TOKEN"]!
|
let token = ProcessInfo.processInfo.environment["DISCORD_TOKEN"]!
|
||||||
|
|
||||||
guard !token.isEmpty else { fatalError("Err: Empty DISCORD_TOKEN. Exiting...") }
|
guard !token.isEmpty else { fatalError("Err: Empty DISCORD_TOKEN. Exiting...") }
|
||||||
|
|
||||||
let bot = try await DiscordKit.Bot(token: token)
|
let intents: Intents = [.guilds, .guildMessages, .messageContent, .guildMembers, .directMessages]
|
||||||
|
print(intents)
|
||||||
|
|
||||||
|
let bot = try await DiscordKit.Bot(token: token, intents: intents)
|
||||||
|
|
||||||
// let bot = await BotGatewayManager(token: token, intents: [.guildMessages, .messageContent])
|
// let bot = await BotGatewayManager(token: token, intents: [.guildMessages, .messageContent])
|
||||||
|
|
||||||
//await withTaskGroup(of: Void.self) { taskGroup in
|
await withThrowingTaskGroup(of: Void.self) { taskGroup in
|
||||||
// taskGroup.addTask {
|
taskGroup.addTask {
|
||||||
// await bot.connect()
|
try await bot.connect()
|
||||||
// let tmp = try! await bot.client.getOwnUser()
|
}
|
||||||
// ownID = try! tmp.decode().id
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for await event in await bot.events {
|
taskGroup.addTask {
|
||||||
// taskGroup.addTask {
|
for await event in await bot.events {
|
||||||
// await EventHandler(event: event, client: bot.client).handleAsync()
|
dump(event)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user