diff --git a/src/grom.gleam b/src/grom.gleam index e86bdab..38f58c1 100644 --- a/src/grom.gleam +++ b/src/grom.gleam @@ -91,7 +91,7 @@ fn snowflake_decoder() -> Decoder(Snowflake(a)) { use id <- decode.then(decode.string) case int.parse(id) { Ok(id) -> decode.success(Snowflake(id)) - Error(_) -> decode.failure(Snowflake(id: 0), expected: "Snowflake") + Error(_) -> decode.failure(Snowflake(id: 0), expected: "Snowflake(a)") } } @@ -144,7 +144,7 @@ fn permissions_decoder() -> Decoder(List(Permission)) { }) |> decode.success } - Error(_) -> decode.failure([], expected: "Permission") + Error(_) -> decode.failure([], expected: "List(Permission)") } } @@ -2423,7 +2423,7 @@ pub type Message { /// this will be an empty string. content: String, sent_timestamp: Timestamp, - edited_tmestamp: Option(Timestamp), + edited_timestamp: Option(Timestamp), /// Whether the message is text-to-speech-enabled. is_tts: Bool, mentions_everyone: Bool, @@ -2475,13 +2475,167 @@ pub type Message { ) } +fn message_decoder() -> Decoder(Message) { + use id <- decode.field("id", snowflake_decoder()) + use channel_id <- decode.field("channel_id", snowflake_decoder()) + use author <- decode.field("author", user_decoder()) + use content <- decode.field("content", decode.string) + use sent_timestamp <- decode.field("timestamp", rfc3339_decoder()) + use edited_timestamp <- decode.field( + "edited_timestamp", + decode.optional(rfc3339_decoder()), + ) + use is_tts <- decode.field("tts", decode.bool) + use mentions_everyone <- decode.field("mention_everyone", decode.bool) + use mentioned_users <- decode.field( + "mentions", + decode.list(of: user_decoder()), + ) + use mentioned_roles_ids <- decode.field( + "mention_roles", + decode.list(of: snowflake_decoder()), + ) + use attachments <- decode.field( + "attachments", + decode.list(of: message_attachment_decoder()), + ) + use embeds <- decode.field("embeds", decode.list(of: message_embed_decoder())) + use reactions <- decode.optional_field( + "reactions", + [], + decode.list(of: message_reaction_decoder()), + ) + use nonce <- decode.optional_field( + "nonce", + None, + decode.map(message_nonce_decoder(), Some), + ) + use is_pinned <- decode.field("pinned", decode.bool) + use webhook_id <- decode.optional_field( + "webhook_id", + None, + decode.map(snowflake_decoder(), Some), + ) + use type_ <- decode.field("type", message_type_decoder()) + use application_id <- decode.optional_field( + "application_id", + None, + decode.map(snowflake_decoder(), Some), + ) + use flags <- decode.optional_field( + "flags", + [], + flags_decoder(bits_message_flags()), + ) + use reference <- decode.optional_field( + "message_reference", + None, + decode.map(message_reference_decoder(), Some), + ) + use snapshots <- decode.optional_field( + "message_snapshots", + [], + decode.list(of: message_snapshot_decoder()), + ) + use referenced_message <- decode.optional_field( + "referenced_message", + None, + decode.optional(message_decoder()), + ) + use interaction_metadata <- decode.optional_field( + "interaction_metadata", + None, + decode.map(message_interaction_metadata_decoder(), Some), + ) + use thread <- decode.optional_field( + "thread", + None, + decode.map(thread_decoder(), Some), + ) + use components <- decode.optional_field( + "components", + [], + decode.list(message_component_decoder()), + ) + use sticker_items <- decode.optional_field( + "sticker_items", + [], + decode.list(of: sticker_item_decoder()), + ) + use approximate_position_in_thread <- decode.optional_field( + "position", + None, + decode.map(decode.int, Some), + ) + use role_subscription_purchase_notification <- decode.optional_field( + "role_subscription_data", + None, + decode.map(role_subscription_purchase_notification_decoder(), Some), + ) + use poll <- decode.optional_field( + "poll", + None, + decode.map(poll_decoder(), Some), + ) + use call <- decode.optional_field( + "call", + None, + decode.map(message_call_decoder(), Some), + ) + decode.success(Message( + id:, + channel_id:, + author:, + content:, + sent_timestamp:, + edited_timestamp:, + is_tts:, + mentions_everyone:, + mentioned_users:, + mentioned_roles_ids:, + attachments:, + embeds:, + reactions:, + nonce:, + is_pinned:, + webhook_id:, + type_:, + application_id:, + flags:, + reference:, + snapshots:, + referenced_message:, + components:, + interaction_metadata:, + thread:, + sticker_items:, + approximate_position_in_thread:, + role_subscription_purchase_notification:, + poll:, + call:, + )) +} + pub type MessageCall { MessageCall( participants_ids: List(Snowflake(User)), - end_timestamp: Option(Timestamp), + ended_timestamp: Option(Timestamp), ) } +fn message_call_decoder() -> Decoder(MessageCall) { + use participants_ids <- decode.field( + "participants", + decode.list(of: snowflake_decoder()), + ) + use ended_timestamp <- decode.optional_field( + "ended_timestamp", + None, + decode.optional(rfc3339_decoder()), + ) + decode.success(MessageCall(participants_ids:, ended_timestamp:)) +} + pub type Poll { Poll( question: String, @@ -2492,6 +2646,25 @@ pub type Poll { ) } +fn poll_decoder() -> Decoder(Poll) { + use question <- decode.subfield(["question", "text"], decode.string) + use answers <- decode.field("answers", decode.list(of: poll_answer_decoder())) + use end_timestamp <- decode.field("expiry", rfc3339_decoder()) + use allows_multiselect <- decode.field("allow_multiselect", decode.bool) + use results <- decode.optional_field( + "results", + None, + decode.map(poll_results_decoder(), Some), + ) + decode.success(Poll( + question:, + answers:, + end_timestamp:, + allows_multiselect:, + results:, + )) +} + pub type PollResults { PollResults( /// Whether the votes have been precisely counted. @@ -2500,14 +2673,42 @@ pub type PollResults { ) } +fn poll_results_decoder() -> Decoder(PollResults) { + // YES, this has the `is_` prefix + use is_finalized <- decode.field("is_finalized", decode.bool) + use answer_counts <- decode.field( + "answer_counts", + decode.list(of: poll_answer_count_decoder()), + ) + decode.success(PollResults(is_finalized:, answer_counts:)) +} + pub type PollAnswerCount { PollAnswerCount(answer_id: Int, count: Int, current_user_voted: Bool) } +fn poll_answer_count_decoder() -> Decoder(PollAnswerCount) { + use answer_id <- decode.field("id", decode.int) + use count <- decode.field("count", decode.int) + use current_user_voted <- decode.field("me_voted", decode.bool) + decode.success(PollAnswerCount(answer_id:, count:, current_user_voted:)) +} + pub type PollAnswer { PollAnswer(id: Int, text: String, emoji: Option(Emoji)) } +fn poll_answer_decoder() -> Decoder(PollAnswer) { + use id <- decode.field("answer_id", decode.int) + use text <- decode.subfield(["poll_media", "text"], decode.string) + use emoji <- decode.then(decode.optionally_at( + ["poll_media", "emoji"], + None, + decode.map(emoji_decoder(), Some), + )) + decode.success(PollAnswer(id:, text:, emoji:)) +} + pub type RoleSubscriptionPurchaseNotification { RoleSubscriptionPurchaseNotification( listing_id: Snowflake(Sku), @@ -2519,6 +2720,28 @@ pub type RoleSubscriptionPurchaseNotification { ) } +fn role_subscription_purchase_notification_decoder() -> Decoder( + RoleSubscriptionPurchaseNotification, +) { + use listing_id <- decode.field( + "role_subscription_listing_id", + snowflake_decoder(), + ) + use tier_name <- decode.field("tier_name", decode.string) + use total_months_subscribed <- decode.field( + "total_months_subscribed", + decode.int, + ) + // YES, this has the `is_` prefix + use is_renewal <- decode.field("is_renewal", decode.bool) + decode.success(RoleSubscriptionPurchaseNotification( + listing_id:, + tier_name:, + total_months_subscribed:, + is_renewal:, + )) +} + pub type Interaction pub type MessageInteractionMetadata { @@ -2540,21 +2763,125 @@ pub type MessageInteractionMetadata { ) } +fn message_interaction_metadata_decoder() -> Decoder(MessageInteractionMetadata) { + use interaction_id <- decode.field("id", snowflake_decoder()) + use triggering_user <- decode.field("user", user_decoder()) + use installed_guild_id <- decode.then(decode.optionally_at( + ["authorizing_integration_owners", "0"], + None, + decode.map(snowflake_decoder(), Some), + )) + use installed_user_id <- decode.then(decode.optionally_at( + ["authorizing_integration_owners", "1"], + None, + decode.map(snowflake_decoder(), Some), + )) + use original_response_message_id <- decode.optional_field( + "original_response_message_id", + None, + decode.map(snowflake_decoder(), Some), + ) + use target_user <- decode.optional_field( + "target_user", + None, + decode.map(user_decoder(), Some), + ) + use target_message_id <- decode.optional_field( + "target_message_id", + None, + decode.map(snowflake_decoder(), Some), + ) + decode.success(MessageInteractionMetadata( + interaction_id:, + triggering_user:, + installed_guild_id:, + installed_user_id:, + original_response_message_id:, + target_user:, + target_message_id:, + )) +} + pub type MessageSnapshot { MessageSnapshot( type_: MessageType, + /// If your app doesn't have the `MESSAGE_CONTENT` privileged intent enabled, + /// this will be an empty string. content: String, + /// If your app doesn't have the `MESSAGE_CONTENT` privileged intent enabled, + /// this will be an empty list. embeds: List(MessageEmbed), + /// If your app doesn't have the `MESSAGE_CONTENT` privileged intent enabled, + /// this will be an empty list. attachments: List(MessageAttachment), + sent_timestamp: Timestamp, edited_timestamp: Option(Timestamp), flags: List(MessageFlag), mentioned_users: List(User), mentioned_roles_ids: List(Snowflake(Role)), sticker_items: List(StickerItem), + /// If your app doesn't have the `MESSAGE_CONTENT` privileged intent enabled, + /// this will be an empty list. components: List(MessageComponent), ) } +fn message_snapshot_decoder() -> Decoder(MessageSnapshot) { + decode.at(["message"], { + use type_ <- decode.field("type", message_type_decoder()) + use content <- decode.field("content", decode.string) + use embeds <- decode.field( + "embeds", + decode.list(of: message_embed_decoder()), + ) + use sent_timestamp <- decode.field("timestamp", rfc3339_decoder()) + use edited_timestamp <- decode.field( + "edited_timestamp", + decode.optional(rfc3339_decoder()), + ) + use attachments <- decode.field( + "attachments", + decode.list(of: message_attachment_decoder()), + ) + use flags <- decode.optional_field( + "flags", + [], + flags_decoder(bits_message_flags()), + ) + use mentioned_users <- decode.field( + "mentions", + decode.list(of: user_decoder()), + ) + use mentioned_roles_ids <- decode.field( + "mention_roles", + decode.list(snowflake_decoder()), + ) + use sticker_items <- decode.optional_field( + "sticker_items", + [], + decode.list(sticker_item_decoder()), + ) + use components <- decode.optional_field( + "components", + [], + decode.list(of: message_component_decoder()), + ) + decode.success(MessageSnapshot( + type_:, + content:, + embeds:, + attachments:, + sent_timestamp:, + edited_timestamp:, + flags:, + mentioned_users:, + mentioned_roles_ids:, + sticker_items:, + components:, + )) + }) +} + pub type MessageComponent { MessageActionRowComponent(MessageActionRow) MessageSectionComponent(MessageSection) @@ -2565,6 +2892,26 @@ pub type MessageComponent { MessageContainerComponent(MessageContainer) } +fn message_component_decoder() -> Decoder(MessageComponent) { + use type_ <- decode.field("type", decode.int) + case type_ { + 1 -> decode.map(message_action_row_decoder(), MessageActionRowComponent) + 9 -> decode.map(message_section_decoder(), MessageSectionComponent) + 10 -> + decode.map(message_text_display_decoder(), MessageTextDisplayComponent) + 12 -> + decode.map(message_media_gallery_decoder(), MessageMediaGalleryComponent) + 13 -> decode.map(message_file_decoder(), MessageFileComponent) + 14 -> decode.map(message_separator_decoder(), MessageSeparatorComponent) + 17 -> decode.map(message_container_decoder(), MessageContainerComponent) + _ -> + decode.failure( + MessageTextDisplayComponent(MessageTextDisplay(id: 0, content: "")), + "MessageComponent", + ) + } +} + pub type MessageContainer { MessageContainer( id: Int, @@ -2574,6 +2921,21 @@ pub type MessageContainer { ) } +fn message_container_decoder() -> Decoder(MessageContainer) { + use id <- decode.field("id", decode.int) + use components <- decode.field( + "components", + decode.list(of: container_component_decoder()), + ) + use accent_colour <- decode.optional_field( + "accent_color", + None, + decode.optional(hex_colour_decoder()), + ) + use is_spoiler <- decode.optional_field("spoiler", False, decode.bool) + decode.success(MessageContainer(id:, components:, accent_colour:, is_spoiler:)) +} + pub type ContainerComponent { ContainerActionRow(MessageActionRow) ContainerTextDisplay(MessageTextDisplay) @@ -2583,6 +2945,23 @@ pub type ContainerComponent { ContainerFile(MessageFile) } +fn container_component_decoder() -> Decoder(ContainerComponent) { + use type_ <- decode.field("type", decode.int) + case type_ { + 1 -> decode.map(message_action_row_decoder(), ContainerActionRow) + 9 -> decode.map(message_section_decoder(), ContainerSection) + 10 -> decode.map(message_text_display_decoder(), ContainerTextDisplay) + 12 -> decode.map(message_media_gallery_decoder(), ContainerMediaGallery) + 13 -> decode.map(message_file_decoder(), ContainerFile) + 14 -> decode.map(message_separator_decoder(), ContainerSeparator) + _ -> + decode.failure( + ContainerTextDisplay(MessageTextDisplay(id: 0, content: "")), + "ContainerComponent", + ) + } +} + pub type MessageFile { MessageFile( id: Int, @@ -2593,6 +2972,15 @@ pub type MessageFile { ) } +fn message_file_decoder() -> Decoder(MessageFile) { + use id <- decode.field("id", decode.int) + use media <- decode.field("media", unfurled_media_item_decoder()) + use is_spoiler <- decode.optional_field("spoiler", False, decode.bool) + use file_name <- decode.field("name", decode.string) + use size_bytes <- decode.field("size", decode.int) + decode.success(MessageFile(id:, media:, is_spoiler:, file_name:, size_bytes:)) +} + pub type MessageSection { MessageSection( id: Int, @@ -2601,10 +2989,29 @@ pub type MessageSection { ) } +fn message_section_decoder() -> Decoder(MessageSection) { + use id <- decode.field("id", decode.int) + use components <- decode.field( + "components", + decode.list(of: section_component_decoder()), + ) + use accessory <- decode.field("accessory", section_accessory_decoder()) + decode.success(MessageSection(id:, components:, accessory:)) +} + pub type MessageMediaGallery { MessageMediaGallery(id: Int, items: List(MediaGalleryItem)) } +fn message_media_gallery_decoder() -> Decoder(MessageMediaGallery) { + use id <- decode.field("id", decode.int) + use items <- decode.field( + "items", + decode.list(of: media_gallery_item_decoder()), + ) + decode.success(MessageMediaGallery(id:, items:)) +} + pub type MediaGalleryItem { MediaGalleryItem( media: UnfurledMediaItem, @@ -2613,19 +3020,73 @@ pub type MediaGalleryItem { ) } +fn media_gallery_item_decoder() -> Decoder(MediaGalleryItem) { + use media <- decode.field("media", unfurled_media_item_decoder()) + use description <- decode.optional_field( + "description", + None, + decode.optional(decode.string), + ) + use is_spoiler <- decode.optional_field("spoiler", False, decode.bool) + decode.success(MediaGalleryItem(media:, description:, is_spoiler:)) +} + pub type SectionComponent { SectionTextDisplay(MessageTextDisplay) } +fn section_component_decoder() -> Decoder(SectionComponent) { + use type_ <- decode.field("type", decode.int) + case type_ { + 10 -> decode.map(message_text_display_decoder(), SectionTextDisplay) + _ -> + decode.failure( + SectionTextDisplay(MessageTextDisplay(id: 0, content: "")), + "SectionComponent", + ) + } +} + pub type MessageTextDisplay { MessageTextDisplay(id: Int, content: String) } +fn message_text_display_decoder() -> Decoder(MessageTextDisplay) { + use id <- decode.field("id", decode.int) + use content <- decode.field("content", decode.string) + decode.success(MessageTextDisplay(id:, content:)) +} + pub type SectionAccessory { SectionButton(MessageButton) SectionThumbnail(MessageThumbnail) } +fn section_accessory_decoder() -> Decoder(SectionAccessory) { + use type_ <- decode.field("type", decode.int) + case type_ { + 2 -> decode.map(message_button_decoder(), SectionButton) + 11 -> decode.map(message_thumbnail_decoder(), SectionThumbnail) + _ -> + decode.failure( + SectionThumbnail(MessageThumbnail( + id: 0, + media: UnfurledMediaItem( + url: "", + proxy_url: "", + content_type: "", + flags: [], + metadata: None, + attachment_id: None, + ), + description: None, + is_spoiler: False, + )), + "SectionAccessory", + ) + } +} + pub type MessageThumbnail { MessageThumbnail( id: Int, @@ -2635,6 +3096,18 @@ pub type MessageThumbnail { ) } +fn message_thumbnail_decoder() -> Decoder(MessageThumbnail) { + use id <- decode.field("id", decode.int) + use media <- decode.field("media", unfurled_media_item_decoder()) + use description <- decode.optional_field( + "description", + None, + decode.optional(decode.string), + ) + use is_spoiler <- decode.optional_field("spoiler", False, decode.bool) + decode.success(MessageThumbnail(id:, media:, description:, is_spoiler:)) +} + pub type UnfurledMediaItem { UnfurledMediaItem( url: String, @@ -2648,10 +3121,45 @@ pub type UnfurledMediaItem { ) } +fn unfurled_media_item_decoder() -> Decoder(UnfurledMediaItem) { + use url <- decode.field("url", decode.string) + use proxy_url <- decode.field("proxy_url", decode.string) + use content_type <- decode.field("content_type", decode.string) + use flags <- decode.field( + "flags", + flags_decoder(bits_unfurled_media_item_flags()), + ) + use metadata <- decode.then(unfurled_media_item_metadata_decoder()) + use attachment_id <- decode.optional_field( + "attachment_id", + None, + decode.map(snowflake_decoder(), Some), + ) + decode.success(UnfurledMediaItem( + url:, + proxy_url:, + content_type:, + flags:, + metadata:, + attachment_id:, + )) +} + pub type MessageSeparator { MessageSeparator(id: Int, is_divider_visible: Bool, spacing: SeparatorSpacing) } +fn message_separator_decoder() -> Decoder(MessageSeparator) { + use id <- decode.field("id", decode.int) + use is_divider_visible <- decode.optional_field("divider", True, decode.bool) + use spacing <- decode.optional_field( + "spacing", + SmallSeparatorSpacing, + separator_spacing_decoder(), + ) + decode.success(MessageSeparator(id:, is_divider_visible:, spacing:)) +} + pub type UnfurledMediaItemMetadata { UnfurledMediaItemMetadata( /// In pixels. @@ -2665,14 +3173,53 @@ pub type UnfurledMediaItemMetadata { ) } +fn unfurled_media_item_metadata_decoder() -> Decoder( + Option(UnfurledMediaItemMetadata), +) { + use height <- decode.optional_field( + "height", + None, + decode.optional(decode.int), + ) + case height { + Some(height) -> { + use width <- decode.field("width", decode.int) + use placeholder <- decode.field("placeholder", decode.string) + use placeholder_version <- decode.field("placeholder_version", decode.int) + decode.success( + Some(UnfurledMediaItemMetadata( + height:, + width:, + placeholder:, + placeholder_version:, + )), + ) + } + None -> decode.success(None) + } +} + pub type UnfurledMediaItemFlag { UnfurledMediaItemIsAnimated } +fn bits_unfurled_media_item_flags() -> List(#(Int, UnfurledMediaItemFlag)) { + [#(int.bitwise_shift_left(1, 0), UnfurledMediaItemIsAnimated)] +} + pub type MessageActionRow { MessageActionRow(id: Int, components: List(ActionRowComponent)) } +fn message_action_row_decoder() -> Decoder(MessageActionRow) { + use id <- decode.field("id", decode.int) + use components <- decode.field( + "components", + decode.list(of: action_row_component_decoder()), + ) + decode.success(MessageActionRow(id:, components:)) +} + pub type ActionRowComponent { ActionRowButton(MessageButton) ActionRowStringSelect(MessageStringSelect) @@ -2682,6 +3229,29 @@ pub type ActionRowComponent { ActionRowChannelSelect(MessageChannelSelect) } +fn action_row_component_decoder() -> Decoder(ActionRowComponent) { + use type_ <- decode.field("type", decode.int) + case type_ { + 2 -> decode.map(message_button_decoder(), ActionRowButton) + 3 -> decode.map(message_string_select_decoder(), ActionRowStringSelect) + 5 -> decode.map(message_user_select_decoder(), ActionRowUserSelect) + 6 -> decode.map(message_role_select_decoder(), ActionRowRoleSelect) + 7 -> + decode.map( + message_mentionable_select_decoder(), + ActionRowMentionableSelect, + ) + 8 -> decode.map(message_channel_select_decoder(), ActionRowChannelSelect) + _ -> + decode.failure( + ActionRowButton( + PremiumMessageButton(PremiumButton(0, Snowflake(0), False)), + ), + "ActionRowComponent", + ) + } +} + pub type MessageStringSelect { MessageStringSelect( id: Int, @@ -2694,6 +3264,32 @@ pub type MessageStringSelect { ) } +fn message_string_select_decoder() -> Decoder(MessageStringSelect) { + use id <- decode.field("id", decode.int) + use custom_id <- decode.field("custom_id", decode.string) + use options <- decode.field( + "options", + decode.list(of: string_select_option_decoder()), + ) + use placeholder <- decode.optional_field( + "placeholder", + None, + decode.map(decode.string, Some), + ) + use min_values <- decode.optional_field("min_values", 1, decode.int) + use max_values <- decode.optional_field("max_values", 1, decode.int) + use is_disabled <- decode.optional_field("disabled", False, decode.bool) + decode.success(MessageStringSelect( + id:, + custom_id:, + options:, + placeholder:, + min_values:, + max_values:, + is_disabled:, + )) +} + pub type MessageUserSelect { MessageUserSelect( id: Int, @@ -2706,6 +3302,148 @@ pub type MessageUserSelect { ) } +fn message_user_select_decoder() -> Decoder(MessageUserSelect) { + use id <- decode.field("id", decode.int) + use custom_id <- decode.field("custom_id", decode.string) + use placeholder <- decode.optional_field( + "placeholder", + None, + decode.map(decode.string, Some), + ) + use default_users <- decode.optional_field( + "default_values", + [], + decode.list(of: decode.at(["id"], snowflake_decoder())), + ) + use min_values <- decode.optional_field("min_values", 1, decode.int) + use max_values <- decode.optional_field("max_values", 1, decode.int) + use is_required <- decode.optional_field("required", False, decode.bool) + decode.success(MessageUserSelect( + id:, + custom_id:, + placeholder:, + default_users:, + min_values:, + max_values:, + is_required:, + )) +} + +fn message_role_select_decoder() -> Decoder(MessageRoleSelect) { + use id <- decode.field("id", decode.int) + use custom_id <- decode.field("custom_id", decode.string) + use placeholder <- decode.optional_field( + "placeholder", + None, + decode.map(decode.string, Some), + ) + use default_roles <- decode.optional_field( + "default_values", + [], + decode.list(of: decode.at(["id"], snowflake_decoder())), + ) + use min_values <- decode.optional_field("min_values", 1, decode.int) + use max_values <- decode.optional_field("max_values", 1, decode.int) + use is_required <- decode.optional_field("required", False, decode.bool) + decode.success(MessageRoleSelect( + id:, + custom_id:, + placeholder:, + default_roles:, + min_values:, + max_values:, + is_required:, + )) +} + +fn message_mentionable_select_decoder() -> Decoder(MessageMentionableSelect) { + use id <- decode.field("id", decode.int) + use custom_id <- decode.field("custom_id", decode.string) + use placeholder <- decode.optional_field( + "placeholder", + None, + decode.map(decode.string, Some), + ) + + // easily the most cursed decoder at this stage of the project + use default_mentionables <- decode.optional_field( + "default_values", + [], + decode.list(of: { + // type annotation here is required, otherwise assumes default_roles contains user ids + use id: Snowflake(a) <- decode.field("id", snowflake_decoder()) + + // i fear this may be a crime + use type_ <- decode.field("type", decode.string) + decode.success(#(id, type_)) + }), + ) + + let default_users = + list.filter_map(default_mentionables, fn(mentionable) { + let #(id, type_) = mentionable + case type_ { + "user" -> Ok(id) + _ -> Error(Nil) + } + }) + let default_roles = + list.filter_map(default_mentionables, fn(mentionable) { + let #(id, type_) = mentionable + case type_ { + "role" -> Ok(id) + _ -> Error(Nil) + } + }) + + use min_values <- decode.optional_field("min_values", 1, decode.int) + use max_values <- decode.optional_field("max_values", 1, decode.int) + use is_required <- decode.optional_field("required", False, decode.bool) + decode.success(MessageMentionableSelect( + id:, + custom_id:, + placeholder:, + min_values:, + max_values:, + is_required:, + default_users:, + default_roles:, + )) +} + +fn message_channel_select_decoder() -> Decoder(MessageChannelSelect) { + use id <- decode.field("id", decode.int) + use custom_id <- decode.field("custom_id", decode.string) + use placeholder <- decode.optional_field( + "placeholder", + None, + decode.map(decode.string, Some), + ) + use default_channels <- decode.optional_field( + "default_values", + [], + decode.list(of: decode.at(["id"], snowflake_decoder())), + ) + use channel_types <- decode.optional_field( + "channel_types", + [], + decode.list(of: channel_select_type_decoder()), + ) + use min_values <- decode.optional_field("min_values", 1, decode.int) + use max_values <- decode.optional_field("max_values", 1, decode.int) + use is_required <- decode.optional_field("required", False, decode.bool) + decode.success(MessageChannelSelect( + id:, + custom_id:, + placeholder:, + default_channels:, + channel_types:, + min_values:, + max_values:, + is_required:, + )) +} + pub type MessageRoleSelect { MessageRoleSelect( id: Int, @@ -2745,34 +3483,99 @@ pub type MessageChannelSelect { } pub type MessageButton { - MessageButtonRegular(RegularMessageButton) - MessageButtonLink(LinkMessageButton) - MessageButtonPremium(PremiumMessageButton) + RegularMessageButton(RegularButton) + LinkMessageButton(LinkButton) + PremiumMessageButton(PremiumButton) +} + +fn message_button_decoder() -> Decoder(MessageButton) { + use style <- decode.field("style", decode.int) + case style { + 1 | 2 | 3 | 4 -> decode.map(regular_button_decoder(), RegularMessageButton) + 5 -> decode.map(link_button_decoder(), LinkMessageButton) + 6 -> decode.map(premium_button_decoder(), PremiumMessageButton) + _ -> + decode.failure( + PremiumMessageButton(PremiumButton(0, Snowflake(0), False)), + "MessageButton", + ) + } +} + +pub type RegularButton { + RegularButton( + id: Int, + style: RegularButtonStyle, + label: Option(String), + emoji: Option(ComponentEmoji), + custom_id: String, + is_disabled: Bool, + ) +} + +fn regular_button_decoder() -> Decoder(RegularButton) { + use id <- decode.field("id", decode.int) + use style <- decode.field("style", regular_button_style_decoder()) + use label <- decode.optional_field( + "label", + None, + decode.map(decode.string, Some), + ) + use emoji <- decode.optional_field( + "emoji", + None, + decode.map(component_emoji_decoder(), Some), + ) + use custom_id <- decode.field("custom_id", decode.string) + use is_disabled <- decode.optional_field("disabled", False, decode.bool) + + decode.success(RegularButton( + id:, + style:, + label:, + emoji:, + custom_id:, + is_disabled:, + )) } -pub type RegularMessageButton { - RegularMessageButton( +pub type LinkButton { + LinkButton( id: Int, - style: RegularButtonStyle, label: Option(String), emoji: Option(ComponentEmoji), - custom_id: String, + url: String, is_disabled: Bool, ) } -pub type LinkMessageButton { - LinkMessageButton( - id: Int, - label: Option(String), - emoji: Option(ComponentEmoji), - url: String, - is_disabled: Bool, +fn link_button_decoder() -> Decoder(LinkButton) { + use id <- decode.field("id", decode.int) + use label <- decode.optional_field( + "label", + None, + decode.map(decode.string, Some), + ) + use emoji <- decode.optional_field( + "emoji", + None, + decode.map(component_emoji_decoder(), Some), ) + use url <- decode.field("url", decode.string) + use is_disabled <- decode.optional_field("disabled", False, decode.bool) + + decode.success(LinkButton(id:, label:, emoji:, url:, is_disabled:)) } -pub type PremiumMessageButton { - PremiumMessageButton(id: Int, sku_id: Snowflake(Sku), is_disabled: Bool) +pub type PremiumButton { + PremiumButton(id: Int, sku_id: Snowflake(Sku), is_disabled: Bool) +} + +fn premium_button_decoder() -> Decoder(PremiumButton) { + use id <- decode.field("id", decode.int) + use sku_id <- decode.field("sku_id", snowflake_decoder()) + use is_disabled <- decode.optional_field("disabled", False, decode.bool) + decode.success(PremiumButton(id:, sku_id:, is_disabled:)) } /// A partial representation of a sticker. @@ -2784,6 +3587,13 @@ pub type StickerItem { ) } +fn sticker_item_decoder() -> Decoder(StickerItem) { + use id <- decode.field("id", snowflake_decoder()) + use name <- decode.field("name", decode.string) + use format_type <- decode.field("format_type", sticker_format_type_decoder()) + decode.success(StickerItem(id:, name:, format_type:)) +} + /// Information regarding a crossposted message, channel follow add notification, /// pin notification reply, etc. pub type MessageReference { @@ -2799,14 +3609,54 @@ pub type MessageReference { ) } +fn message_reference_decoder() -> Decoder(MessageReference) { + use type_ <- decode.optional_field("type", None, decode.map(decode.int, Some)) + use original_message_id <- decode.field("message_id", snowflake_decoder()) + use original_channel_id <- decode.field("channel_id", snowflake_decoder()) + use original_guild_id <- decode.optional_field( + "guild_id", + None, + decode.map(snowflake_decoder(), Some), + ) + + case type_ { + Some(0) | None -> + decode.success(ReplyMessageReference( + original_message_id:, + original_channel_id:, + original_guild_id:, + )) + Some(1) -> + decode.success(ForwardMessageReference( + original_message_id:, + original_channel_id:, + original_guild_id:, + )) + _ -> + decode.failure( + ReplyMessageReference( + original_message_id: Snowflake(0), + original_channel_id: Snowflake(0), + original_guild_id: None, + ), + "MessageReference", + ) + } +} + pub type MessageFlag { MessageIsCrossposted MessageIsCrosspost MessageHasSuppressedEmbeds + /// The message came from a followed announcement channel, and the original message has been deleted. MessageSourceDeleted + /// This message came from Discord's urgent message system MessageIsUrgent + /// The message has an associated thread - its ID is the same as the message's. MessageHasThread + /// The message is only visible to its interaction's invoker. MessageIsEphemeral + /// The message is an interaction response and the bot is "thinking". MessageIsLoading MessageFailedToMentionSomeRolesInThread MessageHasSuppressedNotifications @@ -2815,6 +3665,24 @@ pub type MessageFlag { MessageUsesComponentsV2 } +fn bits_message_flags() -> List(#(Int, MessageFlag)) { + [ + #(int.bitwise_shift_left(1, 0), MessageIsCrossposted), + #(int.bitwise_shift_left(1, 1), MessageIsCrosspost), + #(int.bitwise_shift_left(1, 2), MessageHasSuppressedEmbeds), + #(int.bitwise_shift_left(1, 3), MessageSourceDeleted), + #(int.bitwise_shift_left(1, 4), MessageIsUrgent), + #(int.bitwise_shift_left(1, 5), MessageHasThread), + #(int.bitwise_shift_left(1, 6), MessageIsEphemeral), + #(int.bitwise_shift_left(1, 7), MessageIsLoading), + #(int.bitwise_shift_left(1, 8), MessageFailedToMentionSomeRolesInThread), + #(int.bitwise_shift_left(1, 12), MessageHasSuppressedNotifications), + #(int.bitwise_shift_left(1, 13), MessageIsVoiceMessage), + #(int.bitwise_shift_left(1, 14), MessageHasSnapshot), + #(int.bitwise_shift_left(1, 15), MessageUsesComponentsV2), + ] +} + pub type MessageType { /// Deletable. MessageIsNormalMessage @@ -2892,6 +3760,51 @@ pub type MessageType { MessageIsPollResult } +fn message_type_decoder() -> Decoder(MessageType) { + use variant <- decode.then(decode.int) + case variant { + 0 -> decode.success(MessageIsNormalMessage) + 1 -> decode.success(MessageIsRecipientAddNotification) + 2 -> decode.success(MessageIsRecipientRemoveNotification) + 3 -> decode.success(MessageIsCallNotification) + 4 -> decode.success(MessageIsChannelNameChangeNotification) + 5 -> decode.success(MessageIsChannelIconChangeNotification) + 6 -> decode.success(MessageIsPinnedMessageNotification) + 7 -> decode.success(MessageIsUserJoinNotification) + 8 -> decode.success(MessageIsGuildBoostNotification) + 9 -> decode.success(MessageIsGuildBoostTier1Notification) + 10 -> decode.success(MessageIsGuildBoostTier2Notification) + 11 -> decode.success(MessageIsGuildBoostTier3Notification) + 12 -> decode.success(MessageIsChannelFollowAddedNotification) + 14 -> decode.success(MessageIsGuildDiscoveryDisqualificationNotification) + 15 -> decode.success(MessageIsGuildDiscoveryRequalificationNotification) + 16 -> decode.success(MessageIsGuildDiscoveryGracePeriodInitialWarning) + 17 -> decode.success(MessageIsGuildDiscoveryGracePeriodFinalWarning) + 18 -> decode.success(MessageIsThreadCreationNotification) + 19 -> decode.success(MessageIsReply) + 20 -> decode.success(MessageIsSlashCommand) + 21 -> decode.success(MessageIsThreadStarterMessage) + 22 -> decode.success(MessageIsGuildInviteReminder) + 23 -> decode.success(MessageIsContextMenuCommand) + 24 -> decode.success(MessageIsAutoModerationActionNotification) + 25 -> decode.success(MessageIsRoleSubscriptionPurchaseNotification) + 26 -> decode.success(MessageIsInteractionPremiumUpsell) + 27 -> decode.success(MessageIsStageStartNotification) + 28 -> decode.success(MessageIsStageEndNotification) + 29 -> decode.success(MessageIsNewStageSpeakerNotification) + 31 -> decode.success(MessageIsNewStageTopicNotification) + 32 -> + decode.success(MessageIsGuildApplicationPremiumSubscriptionNotification) + 36 -> decode.success(MessageIsGuildIncidentAlertModeEnabledNotification) + 37 -> decode.success(MessageIsGuildIncidentAlertModeDisabledNotification) + 38 -> decode.success(MessageIsRaidReportNotification) + 39 -> decode.success(MessageIsFalseAlarmReportNotification) + 44 -> decode.success(MessageIsPurchaseNotification) + 46 -> decode.success(MessageIsPollResult) + _ -> decode.failure(MessageIsNormalMessage, "MessageType") + } +} + pub type Webhook pub type MessageNonce { @@ -2899,6 +3812,12 @@ pub type MessageNonce { StringMessageNonce(String) } +fn message_nonce_decoder() -> Decoder(MessageNonce) { + decode.one_of(decode.map(decode.int, IntMessageNonce), or: [ + decode.map(decode.string, StringMessageNonce), + ]) +} + pub type MessageReaction { MessageReaction( /// Sum of all reactions (normal + super). @@ -2912,6 +3831,28 @@ pub type MessageReaction { ) } +fn message_reaction_decoder() -> Decoder(MessageReaction) { + use count <- decode.field("count", decode.int) + use super_count <- decode.subfield(["count_details", "burst"], decode.int) + use normal_count <- decode.subfield(["count_details", "normal"], decode.int) + use current_user_reacted <- decode.field("me", decode.bool) + use current_user_super_reacted <- decode.field("me_burst", decode.bool) + use emoji <- decode.field("emoji", emoji_decoder()) + use super_colours <- decode.field( + "burst_colors", + decode.list(hex_colour_decoder()), + ) + decode.success(MessageReaction( + count:, + super_count:, + normal_count:, + current_user_reacted:, + current_user_super_reacted:, + emoji:, + super_colours:, + )) +} + pub type MessageAttachment { MessageAttachment( id: Snowflake(MessageAttachment), @@ -2922,16 +3863,61 @@ pub type MessageAttachment { size_bytes: Int, url: String, proxy_url: String, + is_ephemeral: Bool, /// If the attachment is an image, video, audio, voice message or a clip, additional information about it will live here. data: Option(MessageAttachmentData), flags: List(MessageAttachmentFlag), ) } +fn message_attachment_decoder() -> Decoder(MessageAttachment) { + use id <- decode.field("id", snowflake_decoder()) + use file_name <- decode.field("filename", decode.string) + use title <- decode.optional_field( + "title", + None, + decode.map(decode.string, Some), + ) + use description <- decode.optional_field( + "description", + None, + decode.map(decode.string, Some), + ) + use content_type <- decode.optional_field( + "content_type", + None, + decode.map(decode.string, Some), + ) + use size_bytes <- decode.field("size", decode.int) + use url <- decode.field("url", decode.string) + use proxy_url <- decode.field("proxy_url", decode.string) + use is_ephemeral <- decode.optional_field("ephemeral", False, decode.bool) + use data <- decode.then(message_attachment_data_decoder()) + use flags <- decode.optional_field( + "flags", + [], + flags_decoder(bits_message_attachment_flags()), + ) + + decode.success(MessageAttachment( + id:, + file_name:, + title:, + description:, + content_type:, + size_bytes:, + url:, + proxy_url:, + is_ephemeral:, + data:, + flags:, + )) +} + pub type MessageEmbed { MessageEmbed( title: Option(String), - type_: Option(EmbedType), + type_: EmbedType, description: Option(String), url: Option(String), timestamp: Option(Timestamp), @@ -2945,10 +3931,87 @@ pub type MessageEmbed { ) } +fn message_embed_decoder() -> Decoder(MessageEmbed) { + use title <- decode.optional_field( + "title", + None, + decode.map(decode.string, Some), + ) + use type_ <- decode.optional_field("type", RichEmbed, embed_type_decoder()) + use description <- decode.optional_field( + "description", + None, + decode.map(decode.string, Some), + ) + use url <- decode.optional_field("url", None, decode.map(decode.string, Some)) + use timestamp <- decode.optional_field( + "timestamp", + None, + decode.map(rfc3339_decoder(), Some), + ) + use colour <- decode.optional_field( + "color", + None, + decode.map(hex_colour_decoder(), Some), + ) + use footer <- decode.optional_field( + "footer", + None, + decode.map(embed_footer_decoder(), Some), + ) + use image <- decode.optional_field( + "image", + None, + decode.map(embed_image_decoder(), Some), + ) + use thumbnail <- decode.optional_field( + "thumbnail", + None, + decode.map(embed_image_decoder(), Some), + ) + use video <- decode.optional_field( + "video", + None, + decode.map(embed_video_decoder(), Some), + ) + use author <- decode.optional_field( + "author", + None, + decode.map(embed_author_decoder(), Some), + ) + use fields <- decode.optional_field( + "fields", + None, + decode.map(decode.list(of: embed_field_decoder()), Some), + ) + + decode.success(MessageEmbed( + title:, + type_:, + description:, + url:, + timestamp:, + colour:, + footer:, + image:, + thumbnail:, + video:, + author:, + fields:, + )) +} + pub type EmbedField { EmbedField(name: String, value: String, is_inline: Bool) } +fn embed_field_decoder() -> Decoder(EmbedField) { + use name <- decode.field("name", decode.string) + use value <- decode.field("value", decode.string) + use is_inline <- decode.optional_field("inline", False, decode.bool) + decode.success(EmbedField(name:, value:, is_inline:)) +} + pub type EmbedAuthor { EmbedAuthor( name: String, @@ -2958,6 +4021,22 @@ pub type EmbedAuthor { ) } +fn embed_author_decoder() -> Decoder(EmbedAuthor) { + use name <- decode.field("name", decode.string) + use url <- decode.optional_field("url", None, decode.map(decode.string, Some)) + use icon_url <- decode.optional_field( + "icon_url", + None, + decode.map(decode.string, Some), + ) + use proxy_icon_url <- decode.optional_field( + "proxy_icon_url", + None, + decode.map(decode.string, Some), + ) + decode.success(EmbedAuthor(name:, url:, icon_url:, proxy_icon_url:)) +} + pub type EmbedImage { EmbedImage( url: String, @@ -2976,6 +4055,33 @@ pub type EmbedImage { ) } +fn embed_image_decoder() -> Decoder(EmbedImage) { + use url <- decode.field("url", decode.string) + use proxy_url <- decode.field("proxy_url", decode.string) + use height <- decode.field("height", decode.int) + use width <- decode.field("width", decode.int) + use content_type <- decode.field("content_type", decode.string) + use placeholder <- decode.field("placeholder", decode.string) + use placeholder_version <- decode.field("placeholder_version", decode.int) + use description <- decode.optional_field( + "description", + None, + decode.map(decode.string, Some), + ) + use flags <- decode.field("flags", flags_decoder(bits_embed_image_flags())) + decode.success(EmbedImage( + url:, + proxy_url:, + height:, + width:, + content_type:, + placeholder:, + placeholder_version:, + description:, + flags:, + )) +} + pub type EmbedVideo { EmbedVideo( url: String, @@ -2993,10 +4099,39 @@ pub type EmbedVideo { ) } +fn embed_video_decoder() -> Decoder(EmbedVideo) { + use url <- decode.field("url", decode.string) + use proxy_url <- decode.field("proxy_url", decode.string) + use height <- decode.field("height", decode.int) + use width <- decode.field("width", decode.int) + use content_type <- decode.field("content_type", decode.string) + use placeholder <- decode.field("placeholder", decode.string) + use placeholder_version <- decode.field("placeholder_version", decode.int) + use description <- decode.optional_field( + "description", + None, + decode.map(decode.string, Some), + ) + decode.success(EmbedVideo( + url:, + proxy_url:, + height:, + width:, + content_type:, + placeholder:, + placeholder_version:, + description:, + )) +} + pub type EmbedImageFlag { EmbedImageIsAnimated } +fn bits_embed_image_flags() -> List(#(Int, EmbedImageFlag)) { + [#(int.bitwise_shift_left(1, 5), EmbedImageIsAnimated)] +} + pub type EmbedFooter { EmbedFooter( text: String, @@ -3005,6 +4140,22 @@ pub type EmbedFooter { ) } +fn embed_footer_decoder() -> Decoder(EmbedFooter) { + use text <- decode.field("text", decode.string) + use icon_url <- decode.optional_field( + "icon_url", + None, + decode.map(decode.string, Some), + ) + use proxy_icon_url <- decode.optional_field( + "proxy_icon_url", + None, + decode.map(decode.string, Some), + ) + + decode.success(EmbedFooter(text:, icon_url:, proxy_icon_url:)) +} + pub type EmbedType { RichEmbed ImageEmbed @@ -3015,13 +4166,37 @@ pub type EmbedType { PollResultsEmbed } +fn embed_type_decoder() -> Decoder(EmbedType) { + use variant <- decode.then(decode.string) + case variant { + "rich" -> decode.success(RichEmbed) + "image" -> decode.success(ImageEmbed) + "video" -> decode.success(VideoEmbed) + "gifv" -> decode.success(GifEmbed) + "article" -> decode.success(ArticleEmbed) + "link" -> decode.success(LinkEmbed) + "poll_result" -> decode.success(PollResultsEmbed) + _ -> decode.failure(RichEmbed, "EmbedType") + } +} + pub type MessageAttachmentFlag { MessageAttachmentIsThumbnail + /// The attachment is edited using the mobile-only "remix" feature MessageAttachmentIsRemix MessageAttachmentIsSpoiler MessageAttachmentIsAnimated } +fn bits_message_attachment_flags() -> List(#(Int, MessageAttachmentFlag)) { + [ + #(int.bitwise_shift_left(1, 1), MessageAttachmentIsAnimated), + #(int.bitwise_shift_left(1, 2), MessageAttachmentIsRemix), + #(int.bitwise_shift_left(1, 3), MessageAttachmentIsSpoiler), + #(int.bitwise_shift_left(1, 5), MessageAttachmentIsAnimated), + ] +} + pub type MessageAttachmentData { MessageAttachmentImage(ImageMessageAttachment) MessageAttachmentVideo(VideoMessageAttachment) @@ -3030,6 +4205,128 @@ pub type MessageAttachmentData { MessageAttachmentClip(ClipMessageAttachment) } +fn message_attachment_data_decoder() -> Decoder(Option(MessageAttachmentData)) { + decode.one_of( + clip_message_attachment_decoder() + |> decode.map(MessageAttachmentClip) + |> decode.map(Some), + or: [ + video_message_attachment_decoder() + |> decode.map(MessageAttachmentVideo) + |> decode.map(Some), + image_message_attachment_decoder() + |> decode.map(MessageAttachmentImage) + |> decode.map(Some), + voice_message_attachment_decoder() + |> decode.map(MessageAttachmentVoice) + |> decode.map(Some), + audio_message_attachment_decoder() + |> decode.map(MessageAttachmentAudio) + |> decode.map(Some), + decode.success(None), + ], + ) +} + +fn audio_message_attachment_decoder() -> Decoder(AudioMessageAttachment) { + use duration <- decode.field( + "duration_secs", + decode.float + |> decode.map(float.round) + |> decode.map(duration.seconds), + ) + decode.success(AudioMessageAttachment(duration:)) +} + +fn voice_message_attachment_decoder() -> Decoder(VoiceMessageAttachment) { + use duration <- decode.field( + "duration_secs", + decode.float + |> decode.map(float.round) + |> decode.map(duration.seconds), + ) + use waveform <- decode.field("waveform", base64_decoder()) + + decode.success(VoiceMessageAttachment(duration:, waveform:)) +} + +fn image_message_attachment_decoder() -> Decoder(ImageMessageAttachment) { + use height <- decode.field("height", decode.int) + use width <- decode.field("width", decode.int) + use placeholder <- decode.field("placeholder", decode.string) + use placeholder_version <- decode.field("placeholder_version", decode.int) + + decode.success(ImageMessageAttachment( + height:, + width:, + placeholder:, + placeholder_version:, + )) +} + +fn video_message_attachment_decoder() -> Decoder(VideoMessageAttachment) { + use height <- decode.field("height", decode.int) + use width <- decode.field("width", decode.int) + use placeholder <- decode.field("placeholder", decode.string) + use placeholder_version <- decode.field("placeholder_version", decode.int) + use duration <- decode.field( + "duration_secs", + decode.float + |> decode.map(float.round) + |> decode.map(duration.seconds), + ) + + decode.success(VideoMessageAttachment( + height:, + width:, + placeholder:, + placeholder_version:, + duration:, + )) +} + +fn clip_message_attachment_decoder() -> Decoder(ClipMessageAttachment) { + use height <- decode.field("height", decode.int) + use width <- decode.field("width", decode.int) + use placeholder <- decode.field("placeholder", decode.string) + use placeholder_version <- decode.field("placeholder_version", decode.int) + use duration <- decode.field( + "duration_secs", + decode.float + |> decode.map(float.round) + |> decode.map(duration.seconds), + ) + + use participants <- decode.field( + "clip_participants", + decode.list(of: user_decoder()), + ) + use created_timestamp <- decode.field("clip_created_at", rfc3339_decoder()) + use application <- decode.field( + "application", + decode.optional(application_decoder()), + ) + + decode.success(ClipMessageAttachment( + height:, + width:, + placeholder:, + placeholder_version:, + duration:, + participants:, + created_timestamp:, + application:, + )) +} + +fn base64_decoder() -> Decoder(BitArray) { + use string <- decode.then(decode.string) + case bit_array.base64_decode(string) { + Ok(bits) -> decode.success(bits) + Error(_) -> decode.failure(<<>>, "BitArray") + } +} + pub type ImageMessageAttachment { ImageMessageAttachment( /// In pixels. @@ -12260,24 +13557,21 @@ fn create_action_row_component_to_json( } pub type CreateMessageButton { - CreateMessageButtonRegular(CreateRegularMessageButton) - CreateMessageButtonLink(CreateLinkMessageButton) - CreateMessageButtonPremium(CreatePremiumMessageButton) + CreateRegularMessageButton(CreateRegularButton) + CreateLinkMessageButton(CreateLinkButton) + CreatePremiumMessageButton(CreatePremiumButton) } fn create_message_button_to_json(button: CreateMessageButton) -> Json { case button { - CreateMessageButtonRegular(button) -> - create_regular_message_button_to_json(button) - CreateMessageButtonLink(button) -> - create_link_message_button_to_json(button) - CreateMessageButtonPremium(button) -> - create_premium_message_button_to_json(button) + CreateRegularMessageButton(button) -> create_regular_button_to_json(button) + CreateLinkMessageButton(button) -> create_link_button_to_json(button) + CreatePremiumMessageButton(button) -> create_premium_button_to_json(button) } } -pub opaque type CreateLinkMessageButton { - CreateLinkMessageButton( +pub opaque type CreateLinkButton { + CreateLinkButton( id: Option(Int), label: Option(String), emoji: Option(ComponentEmoji), @@ -12286,7 +13580,7 @@ pub opaque type CreateLinkMessageButton { ) } -fn create_link_message_button_to_json(create: CreateLinkMessageButton) -> Json { +fn create_link_button_to_json(create: CreateLinkButton) -> Json { [ Ok(#("type", json.int(2))), optional_to_json(create.id, "id", json.int), @@ -12299,55 +13593,45 @@ fn create_link_message_button_to_json(create: CreateLinkMessageButton) -> Json { |> list_to_json_object } -pub fn new_link_message_button(url url: String) -> CreateLinkMessageButton { - CreateLinkMessageButton( - id: None, - label: None, - emoji: None, - url:, - is_disabled: False, - ) +pub fn new_link_button(url url: String) -> CreateLinkButton { + CreateLinkButton(id: None, label: None, emoji: None, url:, is_disabled: False) } -pub fn new_link_message_button_with_id( - create: CreateLinkMessageButton, +pub fn new_link_button_with_id( + create: CreateLinkButton, id: Int, -) -> CreateLinkMessageButton { - CreateLinkMessageButton(..create, id: Some(id)) +) -> CreateLinkButton { + CreateLinkButton(..create, id: Some(id)) } /// Label must be max 80 characters. -pub fn new_link_message_button_with_label( - create: CreateLinkMessageButton, +pub fn new_link_button_with_label( + create: CreateLinkButton, label: String, -) -> CreateLinkMessageButton { - CreateLinkMessageButton(..create, label: Some(label)) +) -> CreateLinkButton { + CreateLinkButton(..create, label: Some(label)) } -pub fn new_link_message_button_with_emoji( - create: CreateLinkMessageButton, +pub fn new_link_button_with_emoji( + create: CreateLinkButton, emoji: ComponentEmoji, -) -> CreateLinkMessageButton { - CreateLinkMessageButton(..create, emoji: Some(emoji)) +) -> CreateLinkButton { + CreateLinkButton(..create, emoji: Some(emoji)) } -pub fn new_disabled_link_message_button( - create: CreateLinkMessageButton, -) -> CreateLinkMessageButton { - CreateLinkMessageButton(..create, is_disabled: True) +pub fn new_disabled_link_button(create: CreateLinkButton) -> CreateLinkButton { + CreateLinkButton(..create, is_disabled: True) } -pub opaque type CreatePremiumMessageButton { - CreatePremiumMessageButton( +pub opaque type CreatePremiumButton { + CreatePremiumButton( id: Option(Int), sku_id: Snowflake(Sku), is_disabled: Bool, ) } -fn create_premium_message_button_to_json( - create: CreatePremiumMessageButton, -) -> Json { +fn create_premium_button_to_json(create: CreatePremiumButton) -> Json { [ Ok(#("type", json.int(2))), optional_to_json(create.id, "id", json.int), @@ -12358,27 +13642,27 @@ fn create_premium_message_button_to_json( |> list_to_json_object } -pub fn new_premium_message_button( +pub fn new_premium_button( for_sku_with_id sku_id: Snowflake(Sku), -) -> CreatePremiumMessageButton { - CreatePremiumMessageButton(None, sku_id, False) +) -> CreatePremiumButton { + CreatePremiumButton(None, sku_id, False) } -pub fn new_premium_message_button_with_id( - create: CreatePremiumMessageButton, +pub fn new_premium_button_with_id( + create: CreatePremiumButton, id: Int, -) -> CreatePremiumMessageButton { - CreatePremiumMessageButton(..create, id: Some(id)) +) -> CreatePremiumButton { + CreatePremiumButton(..create, id: Some(id)) } -pub fn new_disabled_premium_message_button( - create: CreatePremiumMessageButton, -) -> CreatePremiumMessageButton { - CreatePremiumMessageButton(..create, is_disabled: True) +pub fn new_disabled_message_button( + create: CreatePremiumButton, +) -> CreatePremiumButton { + CreatePremiumButton(..create, is_disabled: True) } -pub opaque type CreateRegularMessageButton { - CreateRegularMessageButton( +pub opaque type CreateRegularButton { + CreateRegularButton( id: Option(Int), style: RegularButtonStyle, label: Option(String), @@ -12388,9 +13672,7 @@ pub opaque type CreateRegularMessageButton { ) } -fn create_regular_message_button_to_json( - create: CreateRegularMessageButton, -) -> Json { +fn create_regular_button_to_json(create: CreateRegularButton) -> Json { [ Ok(#("type", json.int(2))), optional_to_json(create.id, "id", json.int), @@ -12403,39 +13685,39 @@ fn create_regular_message_button_to_json( } /// Custom ID must be between 1 and 100 characters. -pub fn new_regular_message_button( +pub fn new_regular_button( custom_id custom_id: String, style style: RegularButtonStyle, -) -> CreateRegularMessageButton { - CreateRegularMessageButton(None, style, None, None, custom_id, False) +) -> CreateRegularButton { + CreateRegularButton(None, style, None, None, custom_id, False) } -pub fn new_regular_message_button_with_id( - create: CreateRegularMessageButton, +pub fn new_regular_button_with_id( + create: CreateRegularButton, id: Int, -) -> CreateRegularMessageButton { - CreateRegularMessageButton(..create, id: Some(id)) +) -> CreateRegularButton { + CreateRegularButton(..create, id: Some(id)) } /// Label must be max 80 characters. -pub fn new_regular_message_button_with_label( - create: CreateRegularMessageButton, +pub fn new_regular_button_with_label( + create: CreateRegularButton, label: String, -) -> CreateRegularMessageButton { - CreateRegularMessageButton(..create, label: Some(label)) +) -> CreateRegularButton { + CreateRegularButton(..create, label: Some(label)) } -pub fn new_regular_message_button_with_emoji( - create: CreateRegularMessageButton, +pub fn new_regular_button_with_emoji( + create: CreateRegularButton, emoji: ComponentEmoji, -) -> CreateRegularMessageButton { - CreateRegularMessageButton(..create, emoji: Some(emoji)) +) -> CreateRegularButton { + CreateRegularButton(..create, emoji: Some(emoji)) } -pub fn new_disabled_regular_message_button( - create: CreateRegularMessageButton, -) -> CreateRegularMessageButton { - CreateRegularMessageButton(..create, is_disabled: True) +pub fn new_disabled_regular_button( + create: CreateRegularButton, +) -> CreateRegularButton { + CreateRegularButton(..create, is_disabled: True) } pub type ComponentEmoji { @@ -12443,6 +13725,21 @@ pub type ComponentEmoji { CustomComponentEmoji(id: Snowflake(Emoji), name: String, is_animated: Bool) } +fn component_emoji_decoder() -> Decoder(ComponentEmoji) { + use name <- decode.field("name", decode.string) + use id <- decode.optional_field( + "id", + None, + decode.optional(snowflake_decoder()), + ) + use is_animated <- decode.optional_field("animated", False, decode.bool) + + case id { + Some(id) -> decode.success(CustomComponentEmoji(id:, name:, is_animated:)) + None -> decode.success(UnicodeComponentEmoji(character: name)) + } +} + fn component_emoji_to_json(emoji: ComponentEmoji) -> Json { case emoji { UnicodeComponentEmoji(character:) -> [#("name", json.string(character))] @@ -12462,6 +13759,17 @@ pub type RegularButtonStyle { DangerRegularButton } +fn regular_button_style_decoder() -> Decoder(RegularButtonStyle) { + use variant <- decode.then(decode.int) + case variant { + 1 -> decode.success(PrimaryRegularButton) + 2 -> decode.success(SecondaryRegularButton) + 3 -> decode.success(SuccessRegularButton) + 4 -> decode.success(DangerRegularButton) + _ -> decode.failure(PrimaryRegularButton, "RegularButtonStyle") + } +} + fn regular_button_style_to_json(style: RegularButtonStyle) -> Json { case style { PrimaryRegularButton -> 1 @@ -12578,6 +13886,33 @@ pub type StringSelectOption { ) } +fn string_select_option_decoder() -> Decoder(StringSelectOption) { + use label <- decode.field("label", decode.string) + use value <- decode.field("value", decode.string) + use description <- decode.optional_field( + "description", + None, + decode.map(decode.string, Some), + ) + use emoji <- decode.optional_field( + "emoji", + None, + decode.map(component_emoji_decoder(), Some), + ) + use is_default_selected <- decode.optional_field( + "default", + False, + decode.bool, + ) + decode.success(StringSelectOption( + label:, + value:, + description:, + emoji:, + is_default_selected:, + )) +} + fn string_select_option_to_json(option: StringSelectOption) -> Json { [ Ok(#("label", json.string(option.label))), @@ -13090,6 +14425,24 @@ pub type ChannelSelectType { SelectMediaChannels } +fn channel_select_type_decoder() -> Decoder(ChannelSelectType) { + use variant <- decode.then(decode.int) + case variant { + 0 -> decode.success(SelectTextChannels) + 1 -> decode.success(SelectDmChannels) + 2 -> decode.success(SelectVoiceChannels) + 4 -> decode.success(SelectCategoryChannels) + 5 -> decode.success(SelectAnnouncementChannels) + 10 -> decode.success(SelectAnnouncementThreads) + 11 -> decode.success(SelectPublicThreads) + 12 -> decode.success(SelectPrivateThreads) + 13 -> decode.success(SelectStageChannels) + 15 -> decode.success(SelectForumChannels) + 16 -> decode.success(SelectMediaChannels) + _ -> decode.failure(SelectTextChannels, "ChannelSelectType") + } +} + fn channel_select_type_to_json(type_: ChannelSelectType) -> Json { case type_ { SelectTextChannels -> 0 @@ -13388,6 +14741,15 @@ pub type SeparatorSpacing { LargeSeparatorSpacing } +fn separator_spacing_decoder() -> Decoder(SeparatorSpacing) { + use variant <- decode.then(decode.int) + case variant { + 1 -> decode.success(SmallSeparatorSpacing) + 2 -> decode.success(LargeSeparatorSpacing) + _ -> decode.failure(SmallSeparatorSpacing, "SeparatorSpacing") + } +} + fn separator_spacing_to_json(spacing: SeparatorSpacing) -> Json { case spacing { SmallSeparatorSpacing -> 1 @@ -13655,3 +15017,28 @@ pub fn get_thread_members_response( decode_with: decode.list(of: thread_member_decoder()), ) } + +/// Behavior: +/// - in DM channels - must be a DM channel between the bot and another user +/// - in guild channels - the bot must have the `AllowViewingChannels` and `AllowReadingMessageHistory` permissions in that channel +/// - in voice channels - in addition to the aforementioned permissions, the bot must also have the `AllowConnectingToVoiceChannels` permission in that channel +pub fn get_message_request( + token token: Token, + channel_with_id channel_id: Snowflake(Channel), + 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), + method: http.Get, + ) +} + +pub fn get_message_response( + response: Response(String), +) -> Result(Message, RestError) { + handle_response(response, decode_with: message_decoder()) +}