diff --git a/README.md b/README.md index 36da12be..06c398ca 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ In [Discord developer portal](https://discord.com/developers/applications): `DiscordBM` comes with full support for all kinds of "interactions" such as slash commands, modals, autocomplete etc... and gives you full control over how you want to use them using type-safe APIs. > You can see Penny as an example of using all kinds of commands in production. -Penny registers the commands [here](https://github.com/vapor/penny-bot/blob/main/CODE/Sources/PennyBOT/CommandsManager.swift) and responds to them [here](https://github.com/vapor/penny-bot/blob/main/CODE/Sources/PennyBOT/Handlers/InteractionHandler.swift). +Penny registers the commands [here](https://github.com/vapor/penny-bot/blob/main/Sources/Penny/CommandsManager.swift) and responds to them [here](https://github.com/vapor/penny-bot/blob/main/Sources/Penny/Handlers/InteractionHandler.swift). In this example you'll only make 2 simple slash commands, so you can get started: @@ -397,6 +397,10 @@ enum LinkSubCommand: String, CaseIterable { * `requireRoleSelect() throws -> SelectMenu` * `requireMentionableSelect() throws -> SelectMenu` * `requireChannelSelect() throws -> ChannelSelectMenu` +* `Interaction.Data` has: + * `requireApplicationCommand() throws -> ApplicationCommand` + * `requireMessageComponent() throws -> MessageComponent` + * `requireModalSubmit() throws -> ModalSubmit` * Swift's `Optional` has a `requireValue() throws` function overload (only in `DiscordBM`). diff --git a/Sources/DiscordHTTP/Endpoints/Endpoint.swift b/Sources/DiscordHTTP/Endpoints/Endpoint.swift index 91c5d31e..2a4d3a85 100644 --- a/Sources/DiscordHTTP/Endpoints/Endpoint.swift +++ b/Sources/DiscordHTTP/Endpoints/Endpoint.swift @@ -7,16 +7,17 @@ public protocol Endpoint: Sendable, CustomStringConvertible { var httpMethod: HTTPMethod { get } /// Interaction endpoints don't count against the global rate limit. /// Even if the global rate-limit is exceeded, you can still respond to interactions. + /// So this is used for interaction endpoints. var countsAgainstGlobalRateLimit: Bool { get } - /// Some endpoints don't require an authorization header because the endpoint url itself - /// contains some kind of authorization token. Like some of the webhook endpoints. + /// Some endpoints don't require an authorization header, sometimes because the endpoint url + /// itself contains some kind of authorization token. Like some of the webhook endpoints. var requiresAuthorizationHeader: Bool { get } /// Path parameters. var parameters: [String] { get } var id: Int { get } } -/// Just to switch between the 2 endpoint types. +/// Just to switch between the 3 endpoint types. public enum AnyEndpoint: Endpoint { case api(APIEndpoint) case cdn(CDNEndpoint) diff --git a/Sources/DiscordHTTP/Endpoints/LooseEndpoint.swift b/Sources/DiscordHTTP/Endpoints/LooseEndpoint.swift index 4030528b..25dc993b 100644 --- a/Sources/DiscordHTTP/Endpoints/LooseEndpoint.swift +++ b/Sources/DiscordHTTP/Endpoints/LooseEndpoint.swift @@ -31,12 +31,12 @@ public struct LooseEndpoint: Endpoint, Hashable { } public func hash(into hasher: inout Hasher) { + /// Only `url` is dynamic, so no need to hash other stuff. hasher.combine(url) - hasher.combine(httpMethod.rawValue) } public var description: String { - #"LooseEndpoint(url: "\#(url)", httpMethod: \#(httpMethod))"# + #"LooseEndpoint(url: "\#(url)")"# } public init(url: String) { diff --git a/Sources/DiscordHTTP/HTTPRateLimiter.swift b/Sources/DiscordHTTP/HTTPRateLimiter.swift index 68e814f1..a2b38e9c 100644 --- a/Sources/DiscordHTTP/HTTPRateLimiter.swift +++ b/Sources/DiscordHTTP/HTTPRateLimiter.swift @@ -37,7 +37,7 @@ actor HTTPRateLimiter { self.reset = reset } - func shouldRequest() -> ShouldRequestResponse { + func shouldRequest() -> ShouldRequest { if remaining > 0 { return .true } else { @@ -146,7 +146,7 @@ actor HTTPRateLimiter { } @usableFromInline - enum ShouldRequestResponse: Sendable { + enum ShouldRequest: Sendable { case `true` case `false` /// Need to wait some seconds if you want to make the request @@ -159,7 +159,7 @@ actor HTTPRateLimiter { /// global rate-limit will be less than the max amount and might not allow you /// to make too many requests per second, when it should. @usableFromInline - func shouldRequest(to endpoint: AnyEndpoint) -> ShouldRequestResponse { + func shouldRequest(to endpoint: AnyEndpoint) -> ShouldRequest { guard minutelyInvalidRequestsLimitAllows() else { return .false } if endpoint.countsAgainstGlobalRateLimit { guard globalRateLimitAllows() else { return .false } diff --git a/Sources/DiscordModels/Protocols/MultipartEncodable.swift b/Sources/DiscordModels/Protocols/MultipartEncodable.swift index 0d4904c4..1a7c3290 100644 --- a/Sources/DiscordModels/Protocols/MultipartEncodable.swift +++ b/Sources/DiscordModels/Protocols/MultipartEncodable.swift @@ -11,7 +11,7 @@ public protocol MultipartEncodable: Encodable { /// By default, DiscordBM encodes the `files` as one field, /// and the rest of the payload as a `payload_json` field, which is what Discord asks. /// However, very few endpoints don't accept that approach. - /// Payloads that set this to `true` shouldn't specify `CodingKeys` + /// Payloads that set this to `true` mustn't specify `CodingKeys` /// to exclude the `files` from `Codable`. static var rawEncodable: Bool { get } } @@ -90,11 +90,14 @@ public struct RawFile: Sendable, Encodable, MultipartPartConvertible { } public var multipart: MultipartPart? { - var part = MultipartPart(headers: [:], body: .init(self.data.readableBytesView)) + var part = MultipartPart(headers: [:], body: self.data) if let type { part.headers.add(name: "Content-Type", value: type) } - part.headers.add(name: "Content-Disposition", value: #"form-data; filename="\#(self.filename)""#) + part.headers.add( + name: "Content-Disposition", + value: #"form-data; filename="\#(self.filename)""# + ) return part } @@ -102,10 +105,7 @@ public struct RawFile: Sendable, Encodable, MultipartPartConvertible { if let header = multipart.headers.first(name: "Content-Disposition") { let parts = header.split(separator: ";").compactMap { part -> (key: Substring, value: Substring)? in - var part = part - if part.first == " " { - part.removeFirst() - } + let part = part.trimmingPrefix(" ") let split = part.split(separator: "=") guard split.count == 2 else { return nil } return (split[0], split[1]) diff --git a/Sources/DiscordModels/Types/Interaction.swift b/Sources/DiscordModels/Types/Interaction.swift index aadf8d18..c9b235e5 100644 --- a/Sources/DiscordModels/Types/Interaction.swift +++ b/Sources/DiscordModels/Types/Interaction.swift @@ -269,6 +269,7 @@ public struct Interaction: Sendable, Codable { case messageComponent(MessageComponent) case modalSubmit(ModalSubmit) + /// Requires an `ApplicationCommand` value or throws `Interaction.Error`. public func requireApplicationCommand() throws -> ApplicationCommand { switch self { case let .applicationCommand(applicationCommand): @@ -278,6 +279,7 @@ public struct Interaction: Sendable, Codable { } } + /// Requires a `MessageComponent` value or throws `Interaction.Error`. public func requireMessageComponent() throws -> MessageComponent { switch self { case let .messageComponent(messageComponent): @@ -287,6 +289,7 @@ public struct Interaction: Sendable, Codable { } } + /// Requires a `ModalSubmit` value or throws `Interaction.Error`. public func requireModalSubmit() throws -> ModalSubmit { switch self { case let .modalSubmit(modalSubmit): @@ -360,7 +363,8 @@ public struct Interaction: Sendable, Codable { ) case .ping: self.data = nil - case .__undocumented: self.data = nil + case .__undocumented: + self.data = nil } self.guild_id = try container.decodeIfPresent(GuildSnowflake.self, forKey: .guild_id) self.channel_id = try container.decodeIfPresent( diff --git a/Tests/DiscordBMTests/HTTPRateLimiter.swift b/Tests/DiscordBMTests/HTTPRateLimiter.swift index fec5bcb8..0da25dda 100644 --- a/Tests/DiscordBMTests/HTTPRateLimiter.swift +++ b/Tests/DiscordBMTests/HTTPRateLimiter.swift @@ -146,7 +146,7 @@ class HTTPRateLimiterTests: XCTestCase { } } -extension HTTPRateLimiter.ShouldRequestResponse: Equatable { +extension HTTPRateLimiter.ShouldRequest: Equatable { public static func == (lhs: Self, rhs: Self) -> Bool { if case .true = lhs, case .true = rhs { return true