diff --git a/src/grom.gleam b/src/grom.gleam index f267c39..b4a95f6 100644 --- a/src/grom.gleam +++ b/src/grom.gleam @@ -111,7 +111,7 @@ pub fn new_snowflake(from id: Int) -> Snowflake(a) { Snowflake(id:) } -pub fn list_to_json_object(list: List(Result(#(String, Json), Nil))) -> Json { +fn list_to_json_object(list: List(Result(#(String, Json), Nil))) -> Json { list |> list.filter_map(function.identity) |> json.object @@ -220,16 +220,13 @@ fn flags_to_int(flags: List(flag), bits_flags: List(#(Int, flag))) -> Int { |> int.sum } -pub fn flags_to_json( - flags: List(flag), - bits_flags: List(#(Int, flag)), -) -> Json { +fn flags_to_json(flags: List(flag), bits_flags: List(#(Int, flag))) -> Json { flags |> flags_to_int(bits_flags) |> json.int } -pub fn flags_to_string_json( +fn flags_to_string_json( permissions: List(flag), bits_flags: List(#(Int, flag)), ) -> Json { @@ -289,7 +286,7 @@ pub type User { ) } -pub fn hex_colour_decoder() -> Decoder(Colour) { +fn hex_colour_decoder() -> Decoder(Colour) { use hex <- decode.then(decode.int) case colour.from_rgb_hex(hex) { @@ -1376,7 +1373,7 @@ fn bits_guild_member_flags() -> List(#(Int, GuildMemberFlag)) { pub fn get_current_user_as_guild_member_request( token token: Token, - for guild_id: Snowflake(Guild), + for_guild_with_id guild_id: Snowflake(Guild), ) -> Request(String) { new_request( token:, @@ -1393,7 +1390,7 @@ pub fn get_current_user_as_guild_member_response( pub fn leave_guild_request( token token: Token, - with_id guild_id: Snowflake(Guild), + guild_with_id guild_id: Snowflake(Guild), ) -> Request(String) { new_request( token:, @@ -5114,7 +5111,7 @@ pub type GuildWelcomeScreenChannelEmoji { pub fn new_guild_welcome_screen_channel( with_id id: Snowflake(Channel), - description description: String, + with_description description: String, ) -> GuildWelcomeScreenChannel { GuildWelcomeScreenChannel(id:, description:, emoji: None) } @@ -16610,7 +16607,7 @@ pub opaque type CreateGuildSticker { /// - stickers must be maximum 320x320px pub fn new_guild_sticker( named name: String, - with_description description: String, + with_with_description description: String, with_tags tags: String, file file: File, ) -> CreateGuildSticker { @@ -18337,8 +18334,7 @@ pub fn command_decoder() -> Decoder(Command) { None, None, False, - False, - False, + [], [], Snowflake(0), )), @@ -18365,10 +18361,8 @@ pub type SlashCommand { /// Some([]) - disabled for everyone by default required_permissions: Option(List(Permission)), is_nsfw: Bool, - /// Whether the command can be used in guild-installed environments. - is_guild_installed: Bool, - /// Whether the command can be used in user-installed environments. - is_user_installed: Bool, + /// If empty, it defaults to your application's configured installation types. + installation_types: List(CommandInstallationType), /// Describes where the command is usable. /// /// Is empty for guild-specific commands. @@ -18408,25 +18402,11 @@ pub fn slash_command_decoder() -> Decoder(SlashCommand) { decode.optional(flags_string_decoder(bits_permissions())), ) use is_nsfw <- decode.optional_field("nsfw", False, decode.bool) - use integration_types <- decode.optional_field( + use installation_types <- decode.optional_field( "integration_types", [], - decode.list( - decode.one_of(decode.int, or: [ - { - use string <- decode.then(decode.string) - case int.parse(string) { - Ok(int) -> decode.success(int) - Error(_) -> decode.failure(0, "Int") - } - }, - ]), - ), + decode.list(of: command_installation_type_decoder()), ) - - let is_guild_installed = integration_types |> list.contains(0) - let is_user_installed = integration_types |> list.contains(1) - use contexts <- decode.optional_field("contexts", [], { use option <- decode.then( decode.optional(decode.list(of: command_context_decoder())), @@ -18450,10 +18430,9 @@ pub fn slash_command_decoder() -> Decoder(SlashCommand) { options:, required_permissions:, is_nsfw:, - is_guild_installed:, - is_user_installed:, contexts:, version:, + installation_types:, )) } @@ -18470,10 +18449,8 @@ pub type UserCommand { /// Some([]) - disabled for everyone by default required_permissions: Option(List(Permission)), is_nsfw: Bool, - /// Whether the command can be used in guild-installed environments. - is_guild_installed: Bool, - /// Whether the command can be used in user-installed environments. - is_user_installed: Bool, + /// If empty, it defaults to your application's configured installation types. + installation_types: List(CommandInstallationType), /// Is empty for guild-specific commands. contexts: List(CommandContext), version: Snowflake(CommandVersion), @@ -18500,25 +18477,12 @@ pub fn user_command_decoder() -> Decoder(UserCommand) { decode.optional(flags_string_decoder(bits_permissions())), ) use is_nsfw <- decode.optional_field("nsfw", False, decode.bool) - use integration_types <- decode.optional_field( + use installation_types <- decode.optional_field( "integration_types", [], - decode.list( - decode.one_of(decode.int, or: [ - { - use string <- decode.then(decode.string) - case int.parse(string) { - Ok(int) -> decode.success(int) - Error(_) -> decode.failure(0, "Int") - } - }, - ]), - ), + decode.list(of: command_installation_type_decoder()), ) - let is_guild_installed = integration_types |> list.contains(0) - let is_user_installed = integration_types |> list.contains(1) - use contexts <- decode.optional_field( "contexts", [], @@ -18535,10 +18499,9 @@ pub fn user_command_decoder() -> Decoder(UserCommand) { name_localizations:, required_permissions:, is_nsfw:, - is_guild_installed:, - is_user_installed:, contexts:, version:, + installation_types:, )) } @@ -18555,10 +18518,8 @@ pub type MessageCommand { /// Some([]) - disabled for everyone by default required_permissions: Option(List(Permission)), is_nsfw: Bool, - /// Whether the command can be used in guild-installed environments. - is_guild_installed: Bool, - /// Whether the command can be used in user-installed environments. - is_user_installed: Bool, + /// If empty, it defaults to your application's configured installation types. + installation_types: List(CommandInstallationType), /// Is empty for guild-specific commands. contexts: List(CommandContext), version: Snowflake(CommandVersion), @@ -18585,25 +18546,11 @@ pub fn message_command_decoder() -> Decoder(MessageCommand) { decode.optional(flags_string_decoder(bits_permissions())), ) use is_nsfw <- decode.optional_field("nsfw", False, decode.bool) - use integration_types <- decode.optional_field( + use installation_types <- decode.optional_field( "integration_types", [], - decode.list( - decode.one_of(decode.int, or: [ - { - use string <- decode.then(decode.string) - case int.parse(string) { - Ok(int) -> decode.success(int) - Error(_) -> decode.failure(0, "Int") - } - }, - ]), - ), + decode.list(of: command_installation_type_decoder()), ) - - let is_guild_installed = integration_types |> list.contains(0) - let is_user_installed = integration_types |> list.contains(1) - use contexts <- decode.optional_field( "contexts", [], @@ -18620,10 +18567,9 @@ pub fn message_command_decoder() -> Decoder(MessageCommand) { name_localizations:, required_permissions:, is_nsfw:, - is_guild_installed:, - is_user_installed:, contexts:, version:, + installation_types:, )) } @@ -18754,7 +18700,7 @@ pub type SubCommandSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_sub_command( named name: String, - description description: String, + with_description description: String, ) -> SubCommandSlashCommandOption { SubCommandSlashCommandOption( name:, @@ -18862,7 +18808,7 @@ pub type SubCommandGroupSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_sub_command_group( named name: String, - description description: String, + with_description description: String, ) -> SubCommandGroupSlashCommandOption { SubCommandGroupSlashCommandOption( name:, @@ -18976,7 +18922,7 @@ pub type StringSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_string_slash_command_option( named name: String, - description description: String, + with_description description: String, ) -> StringSlashCommandOption { StringSlashCommandOption( name:, @@ -19211,7 +19157,7 @@ pub type IntSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_int_slash_command_option( named name: String, - description description: String, + with_description description: String, ) -> IntSlashCommandOption { IntSlashCommandOption( name:, @@ -19434,7 +19380,7 @@ pub type FloatSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_float_slash_command_option( named name: String, - description description: String, + with_description description: String, ) -> FloatSlashCommandOption { FloatSlashCommandOption( name:, @@ -19662,7 +19608,7 @@ pub type BoolSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_bool_slash_command_option( named name: String, - description description: String, + with_description description: String, ) -> BoolSlashCommandOption { BoolSlashCommandOption( name:, @@ -19758,7 +19704,7 @@ pub type UserSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_user_slash_command_option( named name: String, - description description: String, + with_description description: String, ) -> UserSlashCommandOption { UserSlashCommandOption( name:, @@ -19857,7 +19803,7 @@ pub type ChannelSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_channel_slash_command_option( named name: String, - description description: String, + with_description description: String, ) -> ChannelSlashCommandOption { ChannelSlashCommandOption( name:, @@ -19979,7 +19925,7 @@ pub type RoleSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_role_slash_command_option( named name: String, - description description: String, + with_description description: String, ) -> RoleSlashCommandOption { RoleSlashCommandOption( name:, @@ -20076,7 +20022,7 @@ pub type MentionableSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_mentionable_slash_command_option( named name: String, - description description: String, + with_description description: String, ) -> MentionableSlashCommandOption { MentionableSlashCommandOption( name:, @@ -20180,7 +20126,7 @@ pub type AttachmentSlashCommandOption { /// The description must be between 1 and 100 characters. pub fn new_attachment_slash_command_option( named name: String, - description description: String, + with_description description: String, ) -> AttachmentSlashCommandOption { AttachmentSlashCommandOption( name:, @@ -20456,9 +20402,7 @@ pub opaque type CreateGlobalSlashCommand { description_localizations: Option(Dict(Locale, String)), required_permissions: Option(List(Permission)), options: List(SlashCommandOption), - is_user_installable: Bool, - is_guild_installable: Bool, - contexts: List(CommandContext), + contexts: Option(List(CommandContext)), is_nsfw: Bool, ) } @@ -20509,7 +20453,7 @@ pub fn create_global_slash_command_to_json( /// Description must be between 1 and 100 characters. pub fn new_global_slash_command( named name: String, - description description: String, + with_description description: String, ) -> CreateGlobalSlashCommand { CreateGlobalSlashCommand( name:, @@ -20597,8 +20541,7 @@ pub opaque type CreateGlobalUserCommand { name: String, name_localizations: Option(Dict(Locale, String)), required_permissions: Option(List(Permission)), - is_user_installable: Bool, - is_guild_installable: Bool, + installation_types: Option(List(CommandInstallationType)), contexts: List(CommandContext), is_nsfw: Bool, ) @@ -20607,17 +20550,6 @@ pub opaque type CreateGlobalUserCommand { pub fn create_global_user_command_to_json( create: CreateGlobalUserCommand, ) -> Json { - // if discord adds another integration type ever im gonna be angwy - let integration_types = case - create.is_guild_installable, - create.is_user_installable - { - True, True -> ["0", "1"] - True, False -> ["0"] - False, True -> ["1"] - False, False -> [] - } - [ Ok(#("type", json.int(2))), Ok(#("name", json.string(create.name))), @@ -20632,7 +20564,10 @@ pub fn create_global_user_command_to_json( flags_to_string_json(_, bits_permissions()), ), Ok(#("nsfw", json.bool(create.is_nsfw))), - Ok(#("integration_types", json.array(integration_types, json.string))), + optional_to_json(create.installation_types, "integration_types", json.array( + _, + command_installation_type_to_json, + )), Ok(#("contexts", json.array(create.contexts, command_context_to_json))), ] |> list_to_json_object @@ -20904,3 +20839,148 @@ pub fn get_global_command_response( ) -> Result(Command, RestError) { handle_response(response:, decode_with: command_decoder()) } + +pub opaque type ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand( + name: Option(String), + name_localizations: Modification(Dict(Locale, String)), + description: Option(String), + description_localizations: Modification(Dict(Locale, String)), + required_permissions: Modification(List(Permission)), + options: Option(List(SlashCommandOption)), + is_user_installable: Option(Bool), + is_guild_installable: Option(Bool), + contexts: Option(List(CommandContext)), + is_nsfw: Option(Bool), + ) +} + +pub fn modify_global_slash_command() -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand( + name: None, + name_localizations: Skip, + description: None, + description_localizations: Skip, + required_permissions: Skip, + options: None, + is_user_installable: None, + is_guild_installable: None, + contexts: None, + is_nsfw: None, + ) +} + +/// Name must be between 1 and 32 characters. +pub fn modify_global_slash_command_name_localizations( + modify: ModifyGlobalSlashCommand, + localizations: Dict(Locale, String), +) -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand(..modify, name_localizations: Modify(localizations)) +} + +pub fn delete_global_slash_command_name_localizations( + modify: ModifyGlobalSlashCommand, +) -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand(..modify, name_localizations: Delete) +} + +/// Description must be between 1 and 100 characters. +pub fn modify_global_slash_command_description_localizations( + modify: ModifyGlobalSlashCommand, + localizations: Dict(Locale, String), +) -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand( + ..modify, + description_localizations: Modify(localizations), + ) +} + +/// Use [`delete_global_slash_command_required_permissions`](#delete_global_slash_command_required_permissions) +/// to enable the command for everyone. +/// +/// Passing an empty list will disable the command for everyone. +/// +/// Passing a list with at least one permission will require those permissions to use the command. +pub fn modify_global_slash_command_required_permissions( + modify: ModifyGlobalSlashCommand, + permissions: List(Permission), +) -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand(..modify, required_permissions: Modify(permissions)) +} + +/// Enables the command for everyone. +pub fn delete_global_slash_command_required_permissions( + modify: ModifyGlobalSlashCommand, +) -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand(..modify, required_permissions: Delete) +} + +/// There is a limit of 25 options per command. +pub fn modify_global_slash_command_options( + modify: ModifyGlobalSlashCommand, + options: List(SlashCommandOption), +) -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand(..modify, options: Some(options)) +} + +pub fn modify_global_slash_command_installation_settings( + modify: ModifyGlobalSlashCommand, +) -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand(..modify, is_guild_installable: Some(True)) +} + +pub fn modify_global_slash_command_with_contexts( + modify: ModifyGlobalSlashCommand, + contexts: List(CommandContext), +) -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand(..modify, contexts:) +} + +/// This will only make the command usable in NSFW contexts. +pub fn modify_nsfw_global_slash_command( + modify: ModifyGlobalSlashCommand, +) -> ModifyGlobalSlashCommand { + ModifyGlobalSlashCommand(..modify, is_nsfw: True) +} + +/// See also: [ApplicationInstallationType](#ApplicationInstallationType) +pub type CommandInstallationType { + /// A guild installed command can be used when the application is guild installed. + GuildInstalledCommand + /// A user installed command can be used when the application is user installed. + UserInstalledCommand +} + +pub fn command_installation_type_decoder() -> Decoder(CommandInstallationType) { + use variant <- decode.then(decode.string) + case variant { + "0" -> decode.success(GuildInstalledCommand) + "1" -> decode.success(UserInstalledCommand) + _ -> decode.failure(GuildInstalledCommand, "CommandInstallationType") + } +} + +pub fn command_installation_type_to_json( + type_: CommandInstallationType, +) -> Json { + case type_ { + GuildInstalledCommand -> "0" + UserInstalledCommand -> "1" + } + |> json.string +} + +pub type ApplicationInstallationType { + /// A guild installed application allows all members of the guild to use its functionality. + /// + /// They may also receive permissions in the guild and execute permission-restricted actions, such as kicking members. + GuildInstalledApplication + /// A user installed application only allows the user who installed it to themselves to use its functionality. + /// + /// User installed applications cannot receive guild permissions - they can only respond to interactions. + /// + /// In DMs, the interaction response will be posted to all recipients. + /// + /// In guilds, whether the response will be ephemeral depends on whether the executing user has the `AllowUsingExternalApplications` permission. + UserInstalledApplication +}