diff --git a/examples/src/examples.gleam b/examples/src/examples.gleam index d066864..66d81ad 100644 --- a/examples/src/examples.gleam +++ b/examples/src/examples.gleam @@ -5,14 +5,5 @@ import grom/user pub fn main() { dotenv_gleam.config() - let assert Ok(token) = envoy.get("TOKEN") - let client = client.Client(token: token) - let assert Ok(user) = - client - |> user.get_user("808763186811633735") - - echo user - - let assert Ok(self) = client |> user.get_current_user - echo self + channe } diff --git a/flake.lock b/flake.lock index 46db729..0681c1b 100644 --- a/flake.lock +++ b/flake.lock @@ -2,16 +2,16 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1749794982, - "narHash": "sha256-Kh9K4taXbVuaLC0IL+9HcfvxsSUx8dPB5s5weJcc9pc=", + "lastModified": 1750133334, + "narHash": "sha256-urV51uWH7fVnhIvsZIELIYalMYsyr2FCalvlRTzqWRw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ee930f9755f58096ac6e8ca94a1887e0534e2d81", + "rev": "36ab78dab7da2e4e27911007033713bab534187b", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixos-unstable", + "ref": "nixos-25.05", "type": "indirect" } }, diff --git a/flake.nix b/flake.nix index a8dada2..61350a3 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; + inputs.nixpkgs.url = "nixpkgs/nixos-25.05"; outputs = { nixpkgs, ... diff --git a/src/grom/channel.gleam b/src/grom/channel.gleam index 0ef8ac3..b9e51b6 100644 --- a/src/grom/channel.gleam +++ b/src/grom/channel.gleam @@ -9,7 +9,7 @@ import gleam/time/duration.{type Duration} import gleam/time/timestamp.{type Timestamp} import grom/channel/guild/forum import grom/channel/guild/media -import grom/channel/guild/thread +import grom/channel/guild/thread.{type Thread} import grom/channel/permission_overwrite.{type PermissionOverwrite} import grom/client.{type Client} import grom/error.{type Error} @@ -83,24 +83,7 @@ pub type Channel { current_user_permissions: Option(List(Permission)), default_auto_archive_duration: Duration, ) - Thread( - id: String, - type_: thread.Type, - guild_id: Option(String), - name: String, - last_message_id: Option(String), - rate_limit_per_user: Duration, - parent_id: String, - last_pin_timestamp: Option(Timestamp), - message_count: Int, - member_count: Int, - metadata: thread.Metadata, - current_member: Option(thread.Member), - current_user_permissions: Option(List(Permission)), - flags: List(thread.Flag), - total_message_sent: Int, - applied_tags_ids: Option(List(String)), - ) + Thread(Thread) Stage( id: String, guild_id: String, @@ -262,32 +245,6 @@ pub type VideoQualityMode { HDVideoQuality } -pub type Mention { - TextMention(channel_id: String, guild_id: String, channel_name: String) - VoiceMention(channel_id: String, guild_id: String, channel_name: String) - CategoryMention(channel_id: String, guild_id: String, channel_name: String) - AnnouncementMention( - channel_id: String, - guild_id: String, - channel_name: String, - ) - AnnouncementThreadMention( - channel_id: String, - guild_id: String, - channel_name: String, - ) - PublicThreadMention( - channel_id: String, - guild_id: String, - channel_name: String, - ) - PrivateThreadMention( - channel_id: String, - guild_id: String, - channel_name: String, - ) -} - // DECODERS -------------------------------------------------------------------- @internal @@ -299,7 +256,10 @@ pub fn decoder() -> decode.Decoder(Channel) { 2 -> voice_decoder() 4 -> category_decoder() 5 -> announcement_decoder() - 10 | 11 | 12 -> thread_decoder() + 10 | 11 | 12 -> { + use thread <- decode.then(thread.decoder()) + decode.success(Thread(thread)) + } 13 -> stage_decoder() 15 -> forum_decoder() 16 -> media_decoder() @@ -534,69 +494,6 @@ pub fn announcement_decoder() -> decode.Decoder(Channel) { )) } -@internal -pub fn thread_decoder() -> decode.Decoder(Channel) { - use id <- decode.field("id", decode.string) - use type_ <- decode.field("type", thread.type_decoder()) - use guild_id <- decode.optional_field( - "guild_id", - None, - decode.optional(decode.string), - ) - use name <- decode.field("name", decode.string) - use last_message_id <- decode.field( - "last_message_id", - decode.optional(decode.string), - ) - use rate_limit_per_user <- decode.field( - "rate_limit_per_user", - time_duration.from_minutes_decoder(), - ) - use parent_id <- decode.field("parent_id", decode.string) - use last_pin_timestamp <- decode.field( - "last_pin_timestamp", - decode.optional(time_rfc3339.decoder()), - ) - use message_count <- decode.field("message_count", decode.int) - use member_count <- decode.field("member_count", decode.int) - use metadata <- decode.field("metadata", thread.metadata_decoder()) - use current_member <- decode.optional_field( - "current_member", - None, - decode.optional(thread.member_decoder()), - ) - use current_user_permissions <- decode.optional_field( - "permissions", - None, - decode.optional(permission.decoder()), - ) - use flags <- decode.field("flags", flags.decoder(thread.bits_flags())) - use total_message_sent <- decode.field("total_message_sent", decode.int) - use applied_tags_ids <- decode.optional_field( - "applied_tags_ids", - None, - decode.optional(decode.list(decode.string)), - ) - decode.success(Thread( - id:, - type_:, - guild_id:, - name:, - last_message_id:, - rate_limit_per_user:, - parent_id:, - last_pin_timestamp:, - message_count:, - member_count:, - metadata:, - current_member:, - current_user_permissions:, - flags:, - total_message_sent:, - applied_tags_ids:, - )) -} - @internal pub fn stage_decoder() -> decode.Decoder(Channel) { use id <- decode.field("id", decode.string) @@ -820,11 +717,6 @@ pub fn followed_channel_decoder() -> decode.Decoder(FollowedChannel) { decode.success(FollowedChannel(channel_id:, webhook_id:)) } -@internal -pub fn mention_decoder() -> decode.Decoder(Mention) { - todo -} - // ENCODERS -------------------------------------------------------------------- @internal @@ -865,7 +757,7 @@ pub fn modify_to_json(modify: Modify) -> Json { modify.permission_overwrites |> modification.encode("permission_overwrites", json.array( _, - permission_overwrite.create_encode, + permission_overwrite.create_to_json, )) let parent_id = @@ -940,7 +832,7 @@ pub fn modify_to_json(modify: Modify) -> Json { modify.permission_overwrites |> modification.encode("permission_overwrites", json.array( _, - permission_overwrite.create_encode, + permission_overwrite.create_to_json, )) let parent_id = @@ -984,7 +876,7 @@ pub fn modify_to_json(modify: Modify) -> Json { modify.permission_overwrites |> modification.encode("permission_overwrites", json.array( _, - permission_overwrite.create_encode, + permission_overwrite.create_to_json, )) [name, position, permission_overwrites] @@ -1019,7 +911,7 @@ pub fn modify_to_json(modify: Modify) -> Json { modify.permission_overwrites |> modification.encode("permission_overwrites", json.array( _, - permission_overwrite.create_encode, + permission_overwrite.create_to_json, )) let parent_id = @@ -1141,7 +1033,7 @@ pub fn modify_to_json(modify: Modify) -> Json { modify.permission_overwrites |> modification.encode("permission_overwrites", json.array( _, - permission_overwrite.create_encode, + permission_overwrite.create_to_json, )) let parent_id = @@ -1201,7 +1093,7 @@ pub fn modify_to_json(modify: Modify) -> Json { modify.permission_overwrites |> modification.encode("permission_overwrites", json.array( _, - permission_overwrite.create_encode, + permission_overwrite.create_to_json, )) let parent_id = @@ -1307,7 +1199,7 @@ pub fn modify_to_json(modify: Modify) -> Json { modify.permission_overwrites |> modification.encode("permission_overwrites", json.array( _, - permission_overwrite.create_encode, + permission_overwrite.create_to_json, )) let default_auto_archive_duration = diff --git a/src/grom/channel/guild/forum.gleam b/src/grom/channel/guild/forum.gleam index 7073a33..4b1cf0e 100644 --- a/src/grom/channel/guild/forum.gleam +++ b/src/grom/channel/guild/forum.gleam @@ -3,6 +3,11 @@ import gleam/int import gleam/json.{type Json} import gleam/option.{type Option, None, Some} import gleam/time/duration.{type Duration} +import grom/message +import grom/message/allowed_mentions.{type AllowedMentions} +import grom/message/attachment +import grom/message/component.{type Component} +import grom/message/embed.{type Embed} // TYPES ----------------------------------------------------------------------- @@ -24,17 +29,17 @@ pub opaque type StartThread { ) } -// pub opaque type StartThreadMessage { -// StartThreadMessage( -// content: Option(String), -// embeds: Option(List(Embed)), -// allowed_mentions: Option(AllowedMentions), -// components: Option(List(Component)), -// sticker_ids: Option(List(String)), -// attachments: Option(List(attachment.Create)), -// flags: Option(List(message.Flag)), -// ) -// } +pub opaque type StartThreadMessage { + StartThreadMessage( + content: Option(String), + embeds: Option(List(Embed)), + allowed_mentions: Option(AllowedMentions), + components: Option(List(Component)), + sticker_ids: Option(List(String)), + attachments: Option(List(attachment.Create)), + flags: Option(List(message.Flag)), + ) +} pub type Flag { RequiresTag diff --git a/src/grom/message.gleam b/src/grom/message.gleam index 8be14c8..3942d84 100644 --- a/src/grom/message.gleam +++ b/src/grom/message.gleam @@ -1,3 +1,4 @@ +import gleam/dict.{type Dict} import gleam/dynamic/decode import gleam/http import gleam/int @@ -6,9 +7,12 @@ import gleam/option.{type Option, None} import gleam/result import gleam/time/timestamp.{type Timestamp} import grom/application.{type Application} -import grom/channel.{type Channel} +import grom/application/current_application +import grom/channel/guild/thread.{type Thread} import grom/client.{type Client} -import grom/error +import grom/error.{type Error} +import grom/guild/member.{type Member} +import grom/guild/role.{type Role} import grom/internal/flags import grom/internal/rest import grom/internal/time_rfc3339 @@ -21,7 +25,7 @@ import grom/message/interaction_metadata.{type InteractionMetadata} import grom/message/message_reference.{type MessageReference} import grom/message/poll.{type Poll} import grom/message/reaction.{type Reaction} -import grom/message/resolved.{type Resolved} +import grom/permission.{type Permission} import grom/sticker import grom/user.{type User} @@ -40,7 +44,7 @@ pub type Message { mentions_users: List(User), // TODO: Fix this being List(Role), not List(String) mentions_roles: List(String), - mentions_channels: Option(List(channel.Mention)), + mentions_channels: Option(List(ChannelMention)), attachments: List(Attachment), embeds: List(Embed), reactions: Option(List(Reaction)), @@ -56,7 +60,7 @@ pub type Message { snapshots: Option(List(Snapshot)), refrenced_message: Option(Message), interaction_metadata: Option(InteractionMetadata), - thread: Option(Channel), + thread: Option(Thread), components: Option(List(Component)), sticker_items: Option(List(sticker.Item)), position: Option(Int), @@ -153,6 +157,130 @@ pub type Snapshot { ) } +pub type Resolved { + Resolved( + users: Option(Dict(String, User)), + members: Option(Dict(String, Member)), + roles: Option(Dict(String, Role)), + channels: Option(Dict(String, ResolvedChannel)), + messages: Option(Dict(String, Message)), + attachments: Option(Dict(String, Attachment)), + ) +} + +pub type ResolvedChannel { + ResolvedTextChannel( + id: String, + name: String, + current_user_permissions: List(Permission), + ) + ResolvedVoiceChannel( + id: String, + name: String, + current_user_permissions: List(Permission), + ) + ResolvedCategoryChannel( + id: String, + name: String, + current_user_permissions: List(Permission), + ) + ResolvedAnnouncementChannel( + id: String, + name: String, + current_user_permissions: List(Permission), + ) + ResolvedAnnouncementThread( + id: String, + name: String, + current_user_permissions: List(Permission), + metadata: thread.Metadata, + parent_id: String, + ) + ResolvedPublicThread( + id: String, + name: String, + current_user_permissions: List(Permission), + metadata: thread.Metadata, + parent_id: String, + ) + ResolvedPrivateThread( + id: String, + name: String, + current_user_permissions: List(Permission), + metadata: thread.Metadata, + parent_id: String, + ) + ResolvedStageChannel( + id: String, + name: String, + current_user_permissions: List(Permission), + ) + ResolvedForumChannel( + id: String, + name: String, + current_user_permissions: List(Permission), + ) + ResolvedMediaChannel( + id: String, + name: String, + current_user_permissions: List(Permission), + ) +} + +pub type ChannelMention { + TextChannelMention( + // don't mind me, i'm just here for formatting ;) + channel_id: String, + guild_id: String, + channel_name: String, + ) + VoiceChannelMention( + channel_id: String, + guild_id: String, + channel_name: String, + ) + CategoryChannelMention( + channel_id: String, + guild_id: String, + channel_name: String, + ) + AnnouncementChannelMention( + channel_id: String, + guild_id: String, + channel_name: String, + ) + AnnouncementThreadMention( + channel_id: String, + guild_id: String, + channel_name: String, + ) + PublicThreadMention( + channel_id: String, + guild_id: String, + channel_name: String, + ) + PrivateThreadMention( + channel_id: String, + guild_id: String, + channel_name: String, + ) + StageChannelMention( + channel_id: String, + guild_id: String, + channel_name: String, + ) + ForumChannelMention( + channel_id: String, + guild_id: String, + channel_name: String, + ) + MediaChannelMention( + channel_id: String, + guild_id: String, + channel_name: String, + ) +} + // FLAGS ----------------------------------------------------------------------- @internal @@ -197,7 +325,7 @@ pub fn decoder() -> decode.Decoder(Message) { use mentions_channels <- decode.optional_field( "mention_channels", None, - decode.optional(decode.list(channel.mention_decoder())), + decode.optional(decode.list(channel_mention_decoder())), ) use attachments <- decode.field( "attachments", @@ -264,7 +392,7 @@ pub fn decoder() -> decode.Decoder(Message) { use thread <- decode.optional_field( "thread", None, - decode.optional(channel.decoder()), + decode.optional(thread.decoder()), ) use components <- decode.optional_field( "components", @@ -289,7 +417,7 @@ pub fn decoder() -> decode.Decoder(Message) { use resolved <- decode.optional_field( "resolved", None, - decode.optional(resolved.decoder()), + decode.optional(resolved_decoder()), ) use poll <- decode.optional_field( "poll", @@ -469,12 +597,167 @@ pub fn role_subscription_data_decoder() -> decode.Decoder(RoleSubscriptionData) )) } +@internal +pub fn channel_mention_decoder() -> decode.Decoder(ChannelMention) { + use type_ <- decode.field("type", decode.int) + use channel_id <- decode.field("id", decode.string) + use guild_id <- decode.field("guild_id", decode.string) + use channel_name <- decode.field("name", decode.string) + + case type_ { + 0 -> + decode.success(TextChannelMention(channel_id:, guild_id:, channel_name:)) + 2 -> + decode.success(VoiceChannelMention(channel_id:, guild_id:, channel_name:)) + 4 -> + decode.success(CategoryChannelMention( + channel_id:, + guild_id:, + channel_name:, + )) + 5 -> + decode.success(AnnouncementChannelMention( + channel_id:, + guild_id:, + channel_name:, + )) + 10 -> + decode.success(AnnouncementThreadMention( + channel_id:, + guild_id:, + channel_name:, + )) + 11 -> + decode.success(PublicThreadMention(channel_id:, guild_id:, channel_name:)) + 12 -> + decode.success(PrivateThreadMention(channel_id:, guild_id:, channel_name:)) + 13 -> + decode.success(StageChannelMention(channel_id:, guild_id:, channel_name:)) + 15 -> + decode.success(ForumChannelMention(channel_id:, guild_id:, channel_name:)) + 16 -> + decode.success(MediaChannelMention(channel_id:, guild_id:, channel_name:)) + _ -> decode.failure(TextChannelMention("", "", ""), "ChannelMention") + } +} + +@internal +pub fn resolved_channel_decoder() -> decode.Decoder(ResolvedChannel) { + use id <- decode.field("id", decode.string) + use name <- decode.field("name", decode.string) + use type_ <- decode.field("type", decode.int) + use current_user_permissions <- decode.field( + "permissions", + permission.decoder(), + ) + + case type_ { + 0 -> + decode.success(ResolvedTextChannel(id:, name:, current_user_permissions:)) + 2 -> + decode.success(ResolvedVoiceChannel(id:, name:, current_user_permissions:)) + 4 -> + decode.success(ResolvedCategoryChannel( + id:, + name:, + current_user_permissions:, + )) + 5 -> + decode.success(ResolvedAnnouncementChannel( + id:, + name:, + current_user_permissions:, + )) + 10 | 11 | 12 -> { + use metadata <- decode.field("thread_metadata", thread.metadata_decoder()) + use parent_id <- decode.field("parent_id", decode.string) + case type_ { + 10 -> + decode.success(ResolvedAnnouncementThread( + id:, + name:, + current_user_permissions:, + metadata:, + parent_id:, + )) + 11 -> + decode.success(ResolvedPublicThread( + id:, + name:, + current_user_permissions:, + metadata:, + parent_id:, + )) + 12 -> + decode.success(ResolvedPrivateThread( + id:, + name:, + current_user_permissions:, + metadata:, + parent_id:, + )) + _ -> decode.failure(ResolvedTextChannel("", "", []), "ResolvedChannel") + } + } + 13 -> + decode.success(ResolvedStageChannel(id:, name:, current_user_permissions:)) + 15 -> + decode.success(ResolvedForumChannel(id:, name:, current_user_permissions:)) + 16 -> + decode.success(ResolvedMediaChannel(id:, name:, current_user_permissions:)) + _ -> decode.failure(ResolvedTextChannel("", "", []), "ResolvedChannel") + } +} + +@internal +pub fn resolved_decoder() -> decode.Decoder(Resolved) { + use users <- decode.optional_field( + "users", + None, + decode.optional(decode.dict(decode.string, user.decoder())), + ) + use members <- decode.optional_field( + "members", + None, + decode.optional(decode.dict(decode.string, member.decoder())), + ) + use roles <- decode.optional_field( + "roles", + None, + decode.optional(decode.dict(decode.string, role.decoder())), + ) + use channels <- decode.optional_field( + "channels", + None, + decode.optional(decode.dict(decode.string, resolved_channel_decoder())), + ) + use messages <- decode.optional_field( + "messages", + None, + decode.optional(decode.dict(decode.string, decoder())), + ) + use attachments <- decode.optional_field( + "attachments", + None, + decode.optional(decode.dict(decode.string, attachment.decoder())), + ) + + decode.success(Resolved( + users:, + members:, + roles:, + channels:, + messages:, + attachments:, + )) +} + // PUBLIC API FUNCTIONS -------------------------------------------------------- pub fn get_pinned( client: Client, in channel_id: String, -) -> Result(List(Message), error.FlybycordError) { +) -> Result(List(Message), Error) { use response <- result.try( client |> rest.new_request(http.Get, "/channels/" <> channel_id <> "/pins") @@ -483,7 +766,7 @@ pub fn get_pinned( response.body |> json.parse(using: decode.list(decoder())) - |> result.map_error(error.DecodeError) + |> result.map_error(error.CouldNotDecode) } pub fn pin( @@ -491,7 +774,7 @@ pub fn pin( in channel_id: String, id message_id: String, reason reason: Option(String), -) -> Result(Nil, error.FlybycordError) { +) -> Result(Nil, Error) { use _response <- result.try( client |> rest.new_request( @@ -510,7 +793,7 @@ pub fn unpin( from channel_id: String, id message_id: String, reason reason: Option(String), -) -> Result(Nil, error.FlybycordError) { +) -> Result(Nil, Error) { use _response <- result.try( client |> rest.new_request( diff --git a/src/grom/message/activity.gleam b/src/grom/message/activity.gleam index 2bf5f96..c7932a0 100644 --- a/src/grom/message/activity.gleam +++ b/src/grom/message/activity.gleam @@ -4,37 +4,28 @@ import gleam/option.{type Option, None} // TYPES ----------------------------------------------------------------------- pub type Activity { - Activity(type_: Type, party_id: Option(String)) -} - -pub type Type { - Join - Spectate - Listen - JoinRequest + JoinActivity(party_id: Option(String)) + SpectateActivity(party_id: Option(String)) + ListenActivity(party_id: Option(String)) + JoinRequestActivity(party_id: Option(String)) } // DECODERS -------------------------------------------------------------------- @internal pub fn decoder() -> decode.Decoder(Activity) { - use type_ <- decode.field("type", type_decoder()) + use type_ <- decode.field("type", decode.int) use party_id <- decode.optional_field( "party_id", None, decode.optional(decode.string), ) - decode.success(Activity(type_:, party_id:)) -} -@internal -pub fn type_decoder() -> decode.Decoder(Type) { - use variant <- decode.then(decode.int) - case variant { - 1 -> decode.success(Join) - 2 -> decode.success(Spectate) - 3 -> decode.success(Listen) - 5 -> decode.success(JoinRequest) - _ -> decode.failure(Join, "Type") + case type_ { + 1 -> decode.success(JoinActivity(party_id:)) + 2 -> decode.success(SpectateActivity(party_id:)) + 3 -> decode.success(ListenActivity(party_id:)) + 5 -> decode.success(JoinRequestActivity(party_id:)) + _ -> decode.failure(JoinActivity(None), "Activity") } } diff --git a/src/grom/message/component/channel_select.gleam b/src/grom/message/component/channel_select.gleam index f9aabfe..af1a2cf 100644 --- a/src/grom/message/component/channel_select.gleam +++ b/src/grom/message/component/channel_select.gleam @@ -1,12 +1,11 @@ import gleam/dynamic/decode import gleam/option.{type Option, None} -import grom/channel pub type ChannelSelect { ChannelSelect( id: Option(Int), custom_id: String, - channel_types: Option(List(channel.Type)), + channel_types: Option(List(ChannelType)), placeholder: Option(String), default_values: Option(List(DefaultValue)), min_values: Int, @@ -15,6 +14,15 @@ pub type ChannelSelect { ) } +pub type ChannelType { + Text + DM + Category + Announcement + Forum + Media +} + pub type DefaultValue { DefaultValue(id: String) } @@ -26,7 +34,7 @@ pub fn decoder() -> decode.Decoder(ChannelSelect) { use channel_types <- decode.optional_field( "channel_types", None, - decode.optional(decode.list(channel.type_decoder())), + decode.optional(decode.list(channel_type_decoder())), ) use placeholder <- decode.optional_field( "placeholder", @@ -59,3 +67,17 @@ pub fn default_value_decoder() -> decode.Decoder(DefaultValue) { use id <- decode.field("id", decode.string) decode.success(DefaultValue(id:)) } + +@internal +pub fn channel_type_decoder() -> decode.Decoder(ChannelType) { + use variant <- decode.then(decode.int) + case variant { + 0 -> decode.success(Text) + 1 -> decode.success(DM) + 4 -> decode.success(Category) + 5 -> decode.success(Announcement) + 15 -> decode.success(Forum) + 16 -> decode.success(Media) + _ -> decode.failure(Text, "ChannelType") + } +} diff --git a/src/grom/message/resolved.gleam b/src/grom/message/resolved.gleam index d748261..feacba4 100644 --- a/src/grom/message/resolved.gleam +++ b/src/grom/message/resolved.gleam @@ -11,33 +11,6 @@ import grom/user.{type User} // TYPES ----------------------------------------------------------------------- -pub type Resolved { - Resolved( - users: Option(Dict(String, User)), - members: Option(Dict(String, Member)), - roles: Option(Dict(String, Role)), - channels: Option(Dict(String, PartialChannel)), - attachments: Option(Dict(String, Attachment)), - ) -} - -pub type PartialChannel { - PartialChannel( - id: String, - name: String, - type_: channel.Type, - permissions: List(Permission), - ) - PartialThread( - id: String, - name: String, - type_: channel.Type, - permissions: List(Permission), - metadata: thread.Metadata, - parent_id: String, - ) -} - // DECODERS -------------------------------------------------------------------- @internal