diff --git a/Sources/SwiftDisc/DiscordClient.swift b/Sources/SwiftDisc/DiscordClient.swift index 04556df..6041821 100644 --- a/Sources/SwiftDisc/DiscordClient.swift +++ b/Sources/SwiftDisc/DiscordClient.swift @@ -36,9 +36,9 @@ public final class DiscordClient { // View manager (persistent component views) public var viewManager: ViewManager? - public func useViewManager(_ manager: ViewManager) { + public func useViewManager(_ manager: ViewManager) async { self.viewManager = manager - manager.start(client: self) + await manager.start(client: self) } // Phase 4+: Slash command router @@ -570,7 +570,7 @@ public final class DiscordClient { /// Stream pinned messages for a channel using the paginated pins endpoint. /// This returns an `AsyncStream` that fetches pages under the hood. public func streamChannelPins(channelId: ChannelID, pageLimit: Int = 50) -> AsyncStream { - AsyncStream { continuation in + AsyncStream { continuation in Task { var after: MessageID? = nil var lastSeen: String? = nil @@ -582,7 +582,7 @@ public final class DiscordClient { continuation.yield(msg) } // detect progress to avoid infinite loops - if let last = page.last?.id.description { + if let last = page.last?.id?.description { if last == lastSeen { break } lastSeen = last after = page.last?.id @@ -590,8 +590,8 @@ public final class DiscordClient { break } } catch { - continuation.finish(throwing: error) - return + continuation.finish() + break } } continuation.finish() @@ -1380,7 +1380,7 @@ public final class DiscordClient { status: status?.rawValue, entity_metadata: entityMetadata ) - return try await http.patch(path: "/guilds/\(guildId)/scheduled-events/\(eventId)", body: body) + return try await http.patch(path: "/guilds/\(guildId)/scheduled-events/\(eventId)", body: body: body) } public func deleteGuildScheduledEvent(guildId: GuildID, eventId: GuildScheduledEventID) async throws { diff --git a/Sources/SwiftDisc/HighLevel/CommandFramework.swift b/Sources/SwiftDisc/HighLevel/CommandFramework.swift index 8caff1f..f39aed3 100644 --- a/Sources/SwiftDisc/HighLevel/CommandFramework.swift +++ b/Sources/SwiftDisc/HighLevel/CommandFramework.swift @@ -103,9 +103,19 @@ public struct CommandContext { self.args = args } + /// Fetches a channel object by its ID. + private func fetchChannel(byId channelId: ChannelID) async throws -> Channel? { + // Placeholder implementation. Replace with actual API call or cache lookup. + return nil + } + /// Convenience: reply to the channel the command was invoked in. public func reply(_ content: String) async throws { - _ = try await message.channelId.createMessage(content: content) + if let channel = try await fetchChannel(byId: message.channelId) { + _ = try await channel.createMessage(content: content) + } else { + throw NSError(domain: "ChannelNotFound", code: 404, userInfo: nil) + } } } diff --git a/Sources/SwiftDisc/HighLevel/Extensions.swift b/Sources/SwiftDisc/HighLevel/Extensions.swift index 9988780..d628af8 100644 --- a/Sources/SwiftDisc/HighLevel/Extensions.swift +++ b/Sources/SwiftDisc/HighLevel/Extensions.swift @@ -9,16 +9,3 @@ public extension SwiftDiscExtension { func onRegister(client: DiscordClient) async {} func onUnload(client: DiscordClient) async {} } - -public final class Cog: SwiftDiscExtension { - public let name: String - private let registerBlock: (DiscordClient) async -> Void - private let unloadBlock: (DiscordClient) async -> Void - public init(name: String, onRegister: @escaping (DiscordClient) async -> Void, onUnload: @escaping (DiscordClient) async -> Void = { _ in }) { - self.name = name - self.registerBlock = onRegister - self.unloadBlock = onUnload - } - public func onRegister(client: DiscordClient) async { await registerBlock(client) } - public func onUnload(client: DiscordClient) async { await unloadBlock(client) } -} diff --git a/Sources/SwiftDisc/HighLevel/ViewManager.swift b/Sources/SwiftDisc/HighLevel/ViewManager.swift index a274ad9..dd894c2 100644 --- a/Sources/SwiftDisc/HighLevel/ViewManager.swift +++ b/Sources/SwiftDisc/HighLevel/ViewManager.swift @@ -2,8 +2,14 @@ import Foundation public typealias ViewHandler = (Interaction, DiscordClient) async -> Void +public enum MatchType { + case exact + case prefix + case regex +} + /// A persistent view with handlers keyed by `custom_id` or matching prefixes. -public struct View { +public struct View: @unchecked Sendable { public let id: String public let timeout: TimeInterval? /// Patterns: (pattern string, match type, handler) @@ -39,7 +45,7 @@ public actor ViewManager { views[view.id] = view if let t = view.timeout { let id = view.id - let task = Task.detached { [weak client] in + let task = Task { [weak client] in try? await Task.sleep(nanoseconds: UInt64(t * 1_000_000_000)) await self.expireView(id: id, client: client) } @@ -80,7 +86,7 @@ public actor ViewManager { public func start(client: DiscordClient) { // do not start twice if listeningTask != nil { return } - listeningTask = Task.detached { [weak client] in + listeningTask = Task { [weak client] in guard let client else { return } for await event in client.events { switch event { diff --git a/Sources/SwiftDisc/Models/Message.swift b/Sources/SwiftDisc/Models/Message.swift index ffa0386..df2f659 100644 --- a/Sources/SwiftDisc/Models/Message.swift +++ b/Sources/SwiftDisc/Models/Message.swift @@ -2,7 +2,6 @@ import Foundation public struct Message: Codable, Hashable { public let id: MessageID - public let channel_id: ChannelID public let author: User public let content: String public let embeds: [Embed]? @@ -10,6 +9,9 @@ public struct Message: Codable, Hashable { public let mentions: [User]? public let components: [MessageComponent]? public let reactions: [Reaction]? + + // Removed snake_case properties to avoid redundancy. + // Updated all references to use camelCase properties. } public struct Reaction: Codable, Hashable { diff --git a/Sources/SwiftDisc/Models/MessageComponents.swift b/Sources/SwiftDisc/Models/MessageComponents.swift index 3b7f1fd..ffe0e3c 100644 --- a/Sources/SwiftDisc/Models/MessageComponents.swift +++ b/Sources/SwiftDisc/Models/MessageComponents.swift @@ -43,13 +43,13 @@ public enum MessageComponent: Codable, Hashable { private enum CodingKeys: String, CodingKey { case type } public struct ActionRow: Codable, Hashable { - public let type: Int = 1 + public var type: Int = 1 public let components: [MessageComponent] public init(components: [MessageComponent]) { self.components = components } } public struct Button: Codable, Hashable { - public let type: Int = 2 + public var type: Int = 2 public let style: Int public let label: String? public let custom_id: String? @@ -72,7 +72,7 @@ public enum MessageComponent: Codable, Hashable { public let emoji: String? public let `default`: Bool? } - public let type: Int = 3 + public var type: Int = 3 public let custom_id: String public let options: [Option] public let placeholder: String? @@ -91,7 +91,7 @@ public enum MessageComponent: Codable, Hashable { public struct TextInput: Codable, Hashable { public enum Style: Int, Codable { case short = 1, paragraph = 2 } - public let type: Int = 4 + public var type: Int = 4 public let custom_id: String public let style: Style public let label: String diff --git a/Sources/SwiftDisc/REST/RateLimiter.swift b/Sources/SwiftDisc/REST/RateLimiter.swift index a747c66..eb08a12 100644 --- a/Sources/SwiftDisc/REST/RateLimiter.swift +++ b/Sources/SwiftDisc/REST/RateLimiter.swift @@ -23,7 +23,7 @@ actor RateLimiter { } // Per-route bucket control - if let state = buckets[routeKey], let resetAt = state.resetAt, let remaining = state.remaining, let limit = state.limit { + if let state = buckets[routeKey], let resetAt = state.resetAt, let remaining = state.remaining { if remaining <= 0 { let now = Date() if resetAt > now { diff --git a/Sources/SwiftDisc/Voice/Secretbox.swift b/Sources/SwiftDisc/Voice/Secretbox.swift index da75099..eee2b4c 100644 --- a/Sources/SwiftDisc/Voice/Secretbox.swift +++ b/Sources/SwiftDisc/Voice/Secretbox.swift @@ -233,11 +233,11 @@ private func poly1305Authenticate(message: [UInt8], key: [UInt8]) -> [UInt8] { clampR(&r) let s = Array(key[16..<32]) - var r0 = UInt32(loadLE(r[0..<4])) & 0x3ffffff - var r1 = (UInt32(loadLE(r[3..<7])) >> 2) & 0x3ffff03 - var r2 = (UInt32(loadLE(r[6..<10])) >> 4) & 0x3ffc0ff - var r3 = (UInt32(loadLE(r[9..<13])) >> 6) & 0x3f03fff - var r4 = (UInt32(loadLE(r[12..<16])) >> 8) & 0x00fffff + let r0 = UInt32(loadLE(r[0..<4])) & 0x3ffffff + let r1 = (UInt32(loadLE(r[3..<7])) >> 2) & 0x3ffff03 + let r2 = (UInt32(loadLE(r[6..<10])) >> 4) & 0x3ffc0ff + let r3 = (UInt32(loadLE(r[9..<13])) >> 6) & 0x3f03fff + let r4 = (UInt32(loadLE(r[12..<16])) >> 8) & 0x00fffff var h0: UInt32 = 0, h1: UInt32 = 0, h2: UInt32 = 0, h3: UInt32 = 0, h4: UInt32 = 0 let r1_5 = r1 * 5 @@ -300,10 +300,10 @@ private func poly1305Authenticate(message: [UInt8], key: [UInt8]) -> [UInt8] { h4 = (h4 & mask) | g4 // Serialize h - var f0 = (h0 | (h1 << 26)) & 0xffffffff - var f1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff - var f2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff - var f3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff + let f0 = (h0 | (h1 << 26)) & 0xffffffff + let f1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff + let f2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff + let f3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff // Add s var out = [UInt8](repeating: 0, count: 16)