diff --git a/src/grom.gleam b/src/grom.gleam index 5f4a3cd..18b3c59 100644 --- a/src/grom.gleam +++ b/src/grom.gleam @@ -16,6 +16,7 @@ import gleam/string import gleam/time/calendar import gleam/time/duration.{type Duration} import gleam/time/timestamp.{type Timestamp} +import gleam/uri import gleam_community/colour.{type Colour} import multipart_form import multipart_form/field @@ -587,7 +588,11 @@ fn new_request( request.new() |> request.set_scheme(http.Https) |> request.set_host(discord_api_url) - |> request.set_path(discord_api_path <> path) + |> request.set_path( + discord_api_path + <> path + |> uri.percent_encode, + ) |> request.set_method(method) |> request.prepend_header("authorization", token) |> request.prepend_header( @@ -12265,20 +12270,18 @@ pub opaque type RateLimitPerUser { RateLimitPerUser(duration: Duration) } -/// Represents he amount of time between a user has to wait between sending a message or creating a thread. +/// Represents the amount of time between a user has to wait between sending a message or creating a thread. /// /// Bots and members with the `AllowBypassingSlowmode` permission are exempt from slowmode. /// -/// Rate limits per user must be between 0 and 21600 seconds (7.22 hours). Returns `Error(Nil)` if not within that range. -pub fn rate_limit_per_user( - taking duration: Duration, -) -> Result(RateLimitPerUser, Nil) { - let seconds = duration.to_seconds(duration) - - case float.clamp(seconds, 0.0, 21_600.0) == seconds { - True -> Ok(RateLimitPerUser(duration:)) - False -> Error(Nil) - } +/// Rate limits per user must be between 0 and 21600 seconds (7.22 hours). The value will be clamped within the bounds. +pub fn rate_limit_per_user(taking duration: Duration) -> RateLimitPerUser { + duration + |> duration.to_seconds + |> float.round + |> int.clamp(0, 21_600) + |> duration.seconds + |> RateLimitPerUser } pub fn rate_limit_per_user_to_duration(limit: RateLimitPerUser) -> Duration { @@ -15539,3 +15542,124 @@ pub fn new_message_response( ) -> Result(Message, RestError) { handle_response_bits(response, decode_with: message_decoder()) } + +/// Crossposts a message from an announcement channel to all its followers. +/// +/// Required permissions in the announcement channel: +/// - `AllowSendingMessages` - for the current user's messages +/// - `AllowManagingMessages` - for all other messages +pub fn crosspost_message_request( + token token: Token, + from_channel_with_id channel_id: Snowflake(AnnouncementChannel), + message_with_id message_id: Snowflake(Message), +) -> Request(String) { + new_request( + token:, + to: "/channels/" + <> snowflake_to_string(channel_id) + <> "/messages/" + <> snowflake_to_string(message_id) + <> "/crosspost", + method: http.Post, + ) +} + +pub fn crosspost_message_response( + response: Response(String), +) -> Result(Message, RestError) { + handle_response(response, decode_with: message_decoder()) +} + +pub type MessageReactionEmoji { + MessageReactionWithUnicodeEmoji(character: String) + MessageReactionWithCustomEmoji(name: String, id: Snowflake(Emoji)) +} + +fn message_reaction_emoji_to_path(emoji: MessageReactionEmoji) { + case emoji { + MessageReactionWithUnicodeEmoji(character:) -> character + MessageReactionWithCustomEmoji(name:, id:) -> + name <> ":" <> snowflake_to_string(id) + } +} + +/// Required permissions in the target channel: +/// - always - `AllowReadingMessageHistory` +/// - if nobody else reacted to the message with this emoji - `AllowAddingReactions` +pub fn new_message_reaction_request( + token token: Token, + in_channel_with_id channel_id: Snowflake(Channel), + message_with_id message_id: Snowflake(Message), + emoji emoji: MessageReactionEmoji, +) -> Request(String) { + new_request( + token:, + to: "/channels/" + <> snowflake_to_string(channel_id) + <> "/messages/" + <> snowflake_to_string(message_id) + <> "/reactions/" + <> message_reaction_emoji_to_path(emoji) + <> "/@me", + method: http.Put, + ) +} + +pub fn new_message_reaction_response( + response: Response(String), +) -> Result(Nil, RestError) { + handle_no_content_response(response) +} + +pub fn delete_current_user_message_reaction_request( + token token: Token, + in_channel_with_id channel_id: Snowflake(Channel), + from_message_with_id message_id: Snowflake(Message), + emoji emoji: MessageReactionEmoji, +) -> Request(String) { + new_request( + token:, + to: "/channels/" + <> snowflake_to_string(channel_id) + <> "/messages/" + <> snowflake_to_string(message_id) + <> "/reactions/" + <> message_reaction_emoji_to_path(emoji) + <> "/@me", + method: http.Delete, + ) +} + +pub fn delete_current_user_message_reaction_response( + response: Response(String), +) -> Result(Nil, RestError) { + handle_no_content_response(response) +} + +/// Requires the `AllowManagingMessages` permission. +pub fn delete_message_reaction_request( + token token: Token, + in_channel_with_id channel_id: Snowflake(Channel), + from_message_with_id message_id: Snowflake(Message), + by_user_with_id user_id: Snowflake(User), + emoji emoji: MessageReactionEmoji, +) -> Request(String) { + new_request( + token:, + to: "/channels/" + <> snowflake_to_string(channel_id) + <> "/messages/" + <> snowflake_to_string(message_id) + <> "/reactions/" + <> message_reaction_emoji_to_path(emoji) + <> "/" + <> snowflake_to_string(user_id), + method: http.Delete, + ) +} + +pub fn delete_message_reaction_response( + response: Response(String), +) -> Result(Nil, RestError) { + handle_no_content_response(response) +}