import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif public struct ApiClient: Sendable { static let apiUrl: URL = URL(string: "https://discord.com/api/v10")! let token: String @discardableResult private func authReq(_ request: consuming URLRequest) async throws -> (Data, URLResponse) { request.setValue("Bot \(token)", forHTTPHeaderField: "Authorization") request.setValue("DiscordKit (https:\\candy123.moe, v0.0.1)", forHTTPHeaderField: "User-Agent") request.setValue("application/json", forHTTPHeaderField: "Content-Type") let (data, res) = try await URLSession.shared.data(for: request) guard let res = res as? HTTPURLResponse else { throw ApiError.invalidResponse } guard res.statusCode >= 200 && res.statusCode <= 299 else { throw ApiError.badStatus("Status code not 2xx: \(res.statusCode)") } return (data, res) } func getGatewayURL() async throws -> URL { var req = URLRequest(url: ApiClient.apiUrl.appending(path: "/gateway/bot")) req.httpMethod = "GET" let (data, _) = try await authReq(req) let json = JSONDecoder() let decoded = try json.decode(GetGatewayResponse.self, from: data) return decoded.url } public func getOwnUser() async throws -> User { var req = URLRequest(url: ApiClient.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: ApiClient.apiUrl.appending(path: "/channels/\(channelId)/messages")) req.httpMethod = "POST" let json = JSONEncoder() req.httpBody = try json.encode(payload) try await authReq(req) } public func triggerTypingIndicator(channelId: String) async throws { var req = URLRequest(url: ApiClient.apiUrl.appending(path: "/channels/\(channelId)/typing")) req.httpMethod = "POST" try await authReq(req) } } public enum ApiError: Error { case invalidResponse case badStatus(_ message: String) }