feat: sending messages works
This commit is contained in:
@@ -4,12 +4,12 @@ import FoundationNetworking
|
||||
#endif
|
||||
|
||||
public actor Bot {
|
||||
let client: ApiClient
|
||||
public let client: DiscordClient
|
||||
let gateway: GatewayClient
|
||||
let intents: Intents
|
||||
|
||||
public init(token: String, intents: Intents) async throws {
|
||||
client = ApiClient(token: token)
|
||||
client = DiscordClient(token: token)
|
||||
self.intents = intents
|
||||
let gatewayURL = try await client.getGatewayURL()
|
||||
gateway = GatewayClient(gatewayURL: gatewayURL, token: token)
|
||||
|
||||
@@ -3,7 +3,7 @@ import Foundation
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
struct ApiClient {
|
||||
public struct DiscordClient: Sendable {
|
||||
static let apiUrl: URL = URL(string: "https://discord.com/api/v10")!
|
||||
let token: String
|
||||
|
||||
@@ -19,7 +19,7 @@ struct ApiClient {
|
||||
}
|
||||
|
||||
func getGatewayURL() async throws -> URL {
|
||||
var req = URLRequest(url: ApiClient.apiUrl.appending(path: "/gateway/bot"))
|
||||
var req = URLRequest(url: DiscordClient.apiUrl.appending(path: "/gateway/bot"))
|
||||
req.httpMethod = "GET"
|
||||
let (data, _) = try await authReq(req)
|
||||
|
||||
@@ -27,6 +27,26 @@ struct ApiClient {
|
||||
let decoded = try json.decode(GetGatewayResponse.self, from: data)
|
||||
return decoded.url
|
||||
}
|
||||
|
||||
public func getOwnUser() async throws -> User {
|
||||
var req = URLRequest(url: DiscordClient.apiUrl.appending(path: "/users/@me"))
|
||||
req.httpMethod = "GET"
|
||||
let (data, _) = try await authReq(req)
|
||||
|
||||
let json = JSONDecoder()
|
||||
let decoded = try json.decode(User.self, from: data)
|
||||
return decoded
|
||||
}
|
||||
|
||||
public func createMessage(channelId: String, payload: CreateMessageReq) async throws {
|
||||
var req = URLRequest(url: DiscordClient.apiUrl.appending(path: "/channels/\(channelId)/messages"))
|
||||
req.httpMethod = "POST"
|
||||
let json = JSONEncoder()
|
||||
|
||||
req.httpBody = try json.encode(payload)
|
||||
let (data, res) = try await authReq(req)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public enum ApiError: Error {
|
||||
@@ -56,32 +56,44 @@ actor GatewayClient {
|
||||
}
|
||||
|
||||
func getMessage() async throws -> GatewayMessage {
|
||||
let wsMessage = try! await ws.receive()
|
||||
print(wsMessage)
|
||||
guard case .string(let str) = wsMessage else { throw GatewayError.invalidMessage }
|
||||
var strBuffer = ""
|
||||
var gwMessage: GatewayMessage? = nil
|
||||
let json = JSONDecoder()
|
||||
let gwMessage = try json.decode(GatewayMessage.self, from: Data(str.utf8))
|
||||
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!))}"
|
||||
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
|
||||
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!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,27 +35,28 @@ public struct Intents: OptionSet, Sendable {
|
||||
}
|
||||
|
||||
public struct GetGatewayResponse: Codable, Sendable {
|
||||
let url: URL
|
||||
let shards: Int
|
||||
let session_start_limit: SessionStartLimit
|
||||
public let url: URL
|
||||
public let shards: Int
|
||||
public let session_start_limit: SessionStartLimit
|
||||
}
|
||||
|
||||
public struct SessionStartLimit: Codable, Sendable {
|
||||
let total: Int
|
||||
let remaining: Int
|
||||
let reset_after: Int
|
||||
let max_concurrency: Int
|
||||
public let total: Int
|
||||
public let remaining: Int
|
||||
public let reset_after: Int
|
||||
public let max_concurrency: Int
|
||||
}
|
||||
|
||||
public enum GatewayPayload: Decodable, Sendable {
|
||||
case hello(HelloPayload)
|
||||
case hello(GatewayHello)
|
||||
case messageCreate(MessageCreate)
|
||||
}
|
||||
|
||||
public struct GatewayMessage: Decodable, Sendable {
|
||||
let op: Int
|
||||
let d: GatewayPayload?
|
||||
let s: Int?
|
||||
let t: String?
|
||||
public let op: Int
|
||||
public let d: GatewayPayload?
|
||||
public let s: Int?
|
||||
public let t: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case t
|
||||
@@ -71,8 +72,11 @@ public struct GatewayMessage: Decodable, Sendable {
|
||||
t = try container.decode(String?.self, forKey: .t)
|
||||
switch op {
|
||||
case 10:
|
||||
let hello = try container.decode(HelloPayload.self, forKey: .d)
|
||||
let hello = try container.decode(GatewayHello.self, forKey: .d)
|
||||
d = .hello(hello)
|
||||
case 0 where t == "MESSAGE_CREATE":
|
||||
let messageCreate = try container.decode(MessageCreate.self, forKey: .d)
|
||||
d = .messageCreate(messageCreate)
|
||||
default:
|
||||
d = nil
|
||||
break
|
||||
@@ -87,7 +91,46 @@ public struct GatewayMessage: Decodable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct HelloPayload: Codable, Sendable {
|
||||
public struct GatewayHello: Codable, Sendable {
|
||||
let heartbeat_interval: Int
|
||||
}
|
||||
|
||||
public struct CreateMessageReq: Codable, Sendable {
|
||||
public init(content: String? = nil, message_reference: MessageRefrence? = nil) {
|
||||
self.content = content
|
||||
self.message_reference = message_reference
|
||||
}
|
||||
|
||||
public let content: String?
|
||||
public let message_reference: MessageRefrence?
|
||||
}
|
||||
|
||||
public struct MessageRefrence: Codable, Sendable {
|
||||
public init(type: Int? = nil, message_id: String? = nil, channel_id: String? = nil, guild_id: String? = nil) {
|
||||
self.type = type
|
||||
self.message_id = message_id
|
||||
self.channel_id = channel_id
|
||||
self.guild_id = guild_id
|
||||
}
|
||||
|
||||
let type: Int?
|
||||
let message_id: String?
|
||||
let channel_id: String?
|
||||
let guild_id: String?
|
||||
}
|
||||
|
||||
public struct MessageCreate: Codable, Sendable {
|
||||
public let id: String
|
||||
public let channel_id: String
|
||||
public let guild_id: String?
|
||||
public let author: User?
|
||||
public let content: String
|
||||
public let mentions: [User]
|
||||
}
|
||||
|
||||
|
||||
public struct User: Codable, Sendable {
|
||||
public let id: String?
|
||||
public let bot: Bool?
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user