From 0c3e99057fe15034e43737f49013bcc71418f4dc Mon Sep 17 00:00:00 2001 From: kozabrada123 <“kozabrada123@users.noreply.github.com”> Date: Sat, 13 May 2023 21:27:44 +0200 Subject: [PATCH 01/22] Add channel and thread types, start guild --- src/api/types.rs | 652 +++++++++++++++++++++++++++++++++-------------- src/gateway.rs | 115 ++++++++- 2 files changed, 566 insertions(+), 201 deletions(-) diff --git a/src/api/types.rs b/src/api/types.rs index e138eec..833691e 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -6,6 +6,7 @@ I do not feel like re-documenting all of this, as everything is already perfectl use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use serde_json::from_value; use crate::{api::limits::Limits, instance::Instance}; @@ -139,7 +140,154 @@ pub struct UnavailableGuild { unavailable: bool } -#[derive(Serialize, Deserialize, Debug, Default)] +/// See https://discord.com/developers/docs/resources/guild +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct Guild { + pub id: String, + pub name: String, + pub icon: Option, + pub icon_hash: Option, + pub splash: Option, + pub discovery_splash: Option, + pub owner: Option, + pub owner_id: String, + pub permissions: Option, + pub afk_channel_id: Option, + pub afk_timeout: u8, + pub widget_enabled: Option, + pub widget_channel_id: Option, + pub verification_level: u8, + pub default_message_notifications: u8, + pub explicit_content_filter: u8, + pub roles: Vec, + pub emojis: Vec, + pub features: Vec, + pub mfa_level: u8, + pub application_id: Option, + pub system_channel_id: Option, + pub system_channel_flags: u8, + pub rules_channel_id: Option, + pub max_presences: Option, + pub max_members: Option, + pub vanity_url_code: Option, + pub description: Option, + pub banner: Option, + pub premium_tier: u8, + pub premium_subscription_count: Option, + pub preferred_locale: String, + pub public_updates_channel_id: Option, + pub max_video_channel_users: Option, + pub max_stage_video_channel_users: Option, + pub approximate_member_count: Option, + pub approximate_presence_count: Option, + pub welcome_screen: Option, + pub nsfw_level: u8, + pub stickers: Option>, + pub premium_progress_bar_enabled: bool +} + +/// See https://discord.com/developers/docs/topics/gateway-events#guild-create-guild-create-extra-fields +/// This is like [Guild], expect it has extra fields +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildCreateGuild { + pub id: String, + pub name: String, + pub icon: Option, + pub icon_hash: Option, + pub splash: Option, + pub discovery_splash: Option, + pub owner: Option, + pub owner_id: String, + pub permissions: Option, + pub afk_channel_id: Option, + pub afk_timeout: u8, + pub widget_enabled: Option, + pub widget_channel_id: Option, + pub verification_level: u8, + pub default_message_notifications: u8, + pub explicit_content_filter: u8, + pub roles: Vec, + pub emojis: Vec, + pub features: Vec, + pub mfa_level: u8, + pub application_id: Option, + pub system_channel_id: Option, + pub system_channel_flags: u8, + pub rules_channel_id: Option, + pub max_presences: Option, + pub max_members: Option, + pub vanity_url_code: Option, + pub description: Option, + pub banner: Option, + pub premium_tier: u8, + pub premium_subscription_count: Option, + pub preferred_locale: String, + pub public_updates_channel_id: Option, + pub max_video_channel_users: Option, + pub max_stage_video_channel_users: Option, + pub approximate_member_count: Option, + pub approximate_presence_count: Option, + pub welcome_screen: Option, + pub nsfw_level: u8, + pub stickers: Option>, + pub premium_progress_bar_enabled: bool, + // ------ Extra Fields ------ + pub joined_at: DateTime, + pub large: bool, + pub unavailable: Option, + pub member_count: u64, + // to:do implement voice states + //pub voice_states: Vec, + pub members: Vec, + pub channels: Vec, + pub threads: Vec, + pub presences: Vec, + // to:do add stage instances + //pub stage_instances: Vec, + // to:do add guild schedules events + //pub guild_scheduled_events: Vec +} + +impl GuildCreateGuild { + /// Converts self to a [Guild], discarding the extra fields + pub fn to_guild(&self) -> Guild { + let as_value = serde_json::to_value(&self).unwrap(); + return from_value(as_value).unwrap(); + } +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct WelcomeScreenObject { + pub description: Option, + pub welcome_channels: Vec +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct WelcomeScreenChannel { + pub channel_id: String, + pub description: String, + pub emoji_id: Option, + pub emoji_name: Option +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +/// See https://discord.com/developers/docs/topics/permissions#role-object +pub struct RoleObject { + pub id: String, + pub name: String, + pub color: f64, + pub hoist: bool, + pub icon: Option, + pub unicode_emoji: Option, + pub position: u16, + pub permissions: String, + pub managed: bool, + pub mentionable: bool, + // to:do add role tags https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure + //pub tags: Option +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct UserObject { pub id: String, username: String, @@ -447,201 +595,201 @@ struct EmbedField { } #[derive(Debug, Serialize, Deserialize)] -struct Reaction { - count: i32, - me: bool, - emoji: Emoji, +pub struct Reaction { + pub count: i32, + pub me: bool, + pub emoji: Emoji, } -#[derive(Debug, Deserialize, Serialize, Default)] -struct Emoji { - id: Option, - name: Option, - roles: Option>, - user: Option, - require_colons: Option, - managed: Option, - animated: Option, - available: Option, +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +pub struct Emoji { + pub id: Option, + pub name: Option, + pub roles: Option>, + pub user: Option, + pub require_colons: Option, + pub managed: Option, + pub animated: Option, + pub available: Option, } #[derive(Debug, Serialize, Deserialize)] -struct MessageActivity { +pub struct MessageActivity { #[serde(rename = "type")] - activity_type: i64, - party_id: Option, + pub activity_type: i64, + pub party_id: Option, } #[derive(Debug, Deserialize, Serialize)] -struct Application { - id: String, - name: String, - icon: Option, - description: String, - rpc_origins: Option>, - bot_public: bool, - bot_require_code_grant: bool, - terms_of_service_url: Option, - privacy_policy_url: Option, - owner: Option, - summary: String, - verify_key: String, - team: Option, - guild_id: Option, - primary_sku_id: Option, - slug: Option, - cover_image: Option, - flags: Option, - tags: Option>, - install_params: Option, - custom_install_url: Option, - role_connections_verification_url: Option, +pub struct Application { + pub id: String, + pub name: String, + pub icon: Option, + pub description: String, + pub rpc_origins: Option>, + pub bot_public: bool, + pub bot_require_code_grant: bool, + pub terms_of_service_url: Option, + pub privacy_policy_url: Option, + pub owner: Option, + pub summary: String, + pub verify_key: String, + pub team: Option, + pub guild_id: Option, + pub primary_sku_id: Option, + pub slug: Option, + pub cover_image: Option, + pub flags: Option, + pub tags: Option>, + pub install_params: Option, + pub custom_install_url: Option, + pub role_connections_verification_url: Option, } #[derive(Debug, Deserialize, Serialize)] -struct Team { - icon: Option, - id: u64, - members: Vec, - name: String, - owner_user_id: u64, +pub struct Team { + pub icon: Option, + pub id: u64, + pub members: Vec, + pub name: String, + pub owner_user_id: u64, } #[derive(Debug, Deserialize, Serialize)] -struct TeamMember { - membership_state: u8, - permissions: Vec, - team_id: u64, - user: UserObject, +pub struct TeamMember { + pub membership_state: u8, + pub permissions: Vec, + pub team_id: u64, + pub user: UserObject, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] -enum MembershipState { +pub enum MembershipState { Invited = 1, Accepted = 2, } #[derive(Debug, Serialize, Deserialize)] -struct InstallParams { - scopes: Vec, - permissions: String, +pub struct InstallParams { + pub scopes: Vec, + pub permissions: String, } #[derive(Debug, Serialize, Deserialize)] pub struct MessageReference { - message_id: String, - channel_id: String, - guild_id: Option, - fail_if_not_exists: Option, + pub message_id: String, + pub channel_id: String, + pub guild_id: Option, + pub fail_if_not_exists: Option, } #[derive(Debug, Deserialize, Serialize)] -struct MessageInteraction { - id: u64, +pub struct MessageInteraction { + pub id: u64, #[serde(rename = "type")] - interaction_type: u8, - name: String, - user: UserObject, - member: Option, + pub interaction_type: u8, + pub name: String, + pub user: UserObject, + pub member: Option, } -#[derive(Debug, Deserialize, Serialize)] -struct GuildMember { - user: Option, - nick: Option, - avatar: Option, - roles: Vec, - joined_at: String, - premium_since: Option, - deaf: bool, - mute: bool, - flags: i32, - pending: Option, - permissions: Option, - communication_disabled_until: Option, +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct GuildMember { + pub user: Option, + pub nick: Option, + pub avatar: Option, + pub roles: Vec, + pub joined_at: String, + pub premium_since: Option, + pub deaf: bool, + pub mute: bool, + pub flags: i32, + pub pending: Option, + pub permissions: Option, + pub communication_disabled_until: Option, } -#[derive(Debug, Serialize, Deserialize)] -struct Channel { - id: String, +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct Channel { + pub id: String, #[serde(rename = "type")] - channel_type: i32, - guild_id: Option, - position: Option, - permission_overwrites: Option>, - name: Option, - topic: Option, - nsfw: Option, - last_message_id: Option, - bitrate: Option, - user_limit: Option, - rate_limit_per_user: Option, - recipients: Option>, - icon: Option, - owner_id: Option, - application_id: Option, - parent_id: Option, - last_pin_timestamp: Option, - rtc_region: Option, - video_quality_mode: Option, - message_count: Option, - member_count: Option, - thread_metadata: Option, - member: Option, - default_auto_archive_duration: Option, - permissions: Option, - flags: Option, - total_message_sent: Option, - available_tags: Option>, - applied_tags: Option>, - default_reaction_emoji: Option, - default_thread_rate_limit_per_user: Option, - default_sort_order: Option, - default_forum_layout: Option, + pub channel_type: i32, + pub guild_id: Option, + pub position: Option, + pub permission_overwrites: Option>, + pub name: Option, + pub topic: Option, + pub nsfw: Option, + pub last_message_id: Option, + pub bitrate: Option, + pub user_limit: Option, + pub rate_limit_per_user: Option, + pub recipients: Option>, + pub icon: Option, + pub owner_id: Option, + pub application_id: Option, + pub parent_id: Option, + pub last_pin_timestamp: Option, + pub rtc_region: Option, + pub video_quality_mode: Option, + pub message_count: Option, + pub member_count: Option, + pub thread_metadata: Option, + pub member: Option, + pub default_auto_archive_duration: Option, + pub permissions: Option, + pub flags: Option, + pub total_message_sent: Option, + pub available_tags: Option>, + pub applied_tags: Option>, + pub default_reaction_emoji: Option, + pub default_thread_rate_limit_per_user: Option, + pub default_sort_order: Option, + pub default_forum_layout: Option, } -#[derive(Debug, Deserialize, Serialize)] -struct Tag { - id: u64, - name: String, - moderated: bool, - emoji_id: Option, - emoji_name: Option, +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Tag { + pub id: u64, + pub name: String, + pub moderated: bool, + pub emoji_id: Option, + pub emoji_name: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct PermissionOverwrite { - id: String, + pub id: String, #[serde(rename = "type")] - overwrite_type: u8, - allow: String, - deny: String, + pub overwrite_type: u8, + pub allow: String, + pub deny: String, } -#[derive(Debug, Deserialize, Serialize)] -struct ThreadMetadata { - archived: bool, - auto_archive_duration: i32, - archive_timestamp: String, - locked: bool, - invitable: Option, - create_timestamp: Option, +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct ThreadMetadata { + pub archived: bool, + pub auto_archive_duration: i32, + pub archive_timestamp: String, + pub locked: bool, + pub invitable: Option, + pub create_timestamp: Option, } -#[derive(Debug, Deserialize, Serialize)] -struct ThreadMember { - id: Option, - user_id: Option, - join_timestamp: Option, - flags: Option, - member: Option, +#[derive(Default, Debug, Deserialize, Serialize, Clone)] +pub struct ThreadMember { + pub id: Option, + pub user_id: Option, + pub join_timestamp: Option, + pub flags: Option, + pub member: Option, } -#[derive(Debug, Deserialize, Serialize)] -struct DefaultReaction { - emoji_id: Option, - emoji_name: Option, +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct DefaultReaction { + pub emoji_id: Option, + pub emoji_name: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -657,44 +805,44 @@ pub enum Component { } #[derive(Debug, Serialize, Deserialize)] -struct StickerItem { - id: u64, - name: String, - format_type: u8, +pub struct StickerItem { + pub id: u64, + pub name: String, + pub format_type: u8, } -#[derive(Debug, Serialize, Deserialize)] -struct Sticker { - id: u64, - pack_id: Option, - name: String, - description: Option, - tags: String, - asset: Option, +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Sticker { + pub id: u64, + pub pack_id: Option, + pub name: String, + pub description: Option, + pub tags: String, + pub asset: Option, #[serde(rename = "type")] - sticker_type: u8, - format_type: u8, - available: Option, - guild_id: Option, - user: Option, - sort_value: Option, + pub sticker_type: u8, + pub format_type: u8, + pub available: Option, + pub guild_id: Option, + pub user: Option, + pub sort_value: Option, } #[derive(Debug, Serialize, Deserialize)] -struct RoleSubscriptionData { - role_subscription_listing_id: u64, - tier_name: String, - total_months_subscribed: u32, - is_renewal: bool, +pub struct RoleSubscriptionData { + pub role_subscription_listing_id: u64, + pub tier_name: String, + pub total_months_subscribed: u32, + pub is_renewal: bool, } #[derive(Debug, Deserialize, Serialize, Default)] pub struct TypingStartEvent { - channel_id: String, - guild_id: Option, - user_id: String, - timestamp: i64, - member: Option, + pub channel_id: String, + pub guild_id: Option, + pub user_id: String, + pub timestamp: i64, + pub member: Option, } impl WebSocketEvent for TypingStartEvent {} @@ -719,18 +867,28 @@ pub struct GatewayIdentifyConnectionProps { pub device: String, } -#[derive(Debug, Deserialize, Serialize, Default)] +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See https://discord.com/developers/docs/topics/gateway-events#presence-update-presence-update-event-fields pub struct PresenceUpdate { - since: Option, - activities: Vec, - status: String, - afk: Option, + pub user: UserObject, + pub guild_id: String, + pub status: String, + pub activities: Vec, + pub client_status: ClientStatusObject +} + +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +/// See https://discord.com/developers/docs/topics/gateway-events#client-status-object +pub struct ClientStatusObject { + pub desktop: Option, + pub mobile: Option, + pub web: Option } impl WebSocketEvent for PresenceUpdate {} -#[derive(Debug, Deserialize, Serialize)] -struct Activity { +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Activity { name: String, #[serde(rename = "type")] activity_type: i32, @@ -749,19 +907,19 @@ struct Activity { buttons: Option>, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] struct ActivityTimestamps { start: Option, end: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] struct ActivityParty { id: Option, size: Option>, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] struct ActivityAssets { large_image: Option, large_text: Option, @@ -769,7 +927,7 @@ struct ActivityAssets { small_text: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] struct ActivitySecrets { join: Option, spectate: Option, @@ -777,7 +935,7 @@ struct ActivitySecrets { match_string: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] struct ActivityButton { label: String, url: String, @@ -895,6 +1053,126 @@ pub struct UserUpdate { impl WebSocketEvent for UserUpdate {} +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-create +/// Not directly serialized, as the inner payload is a channel object +pub struct ChannelCreate { + pub channel: Channel, +} + +impl WebSocketEvent for ChannelCreate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-update +/// Not directly serialized, as the inner payload is a channel object +pub struct ChannelUpdate { + pub channel: Channel, +} + +impl WebSocketEvent for ChannelUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#channel-delete +/// Not directly serialized, as the inner payload is a channel object +pub struct ChannelDelete { + pub channel: Channel, +} + +impl WebSocketEvent for ChannelDelete {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-create +/// Not directly serialized, as the inner payload is a channel object +pub struct ThreadCreate { + pub thread: Channel, +} + +impl WebSocketEvent for ThreadCreate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-update +/// Not directly serialized, as the inner payload is a channel object +pub struct ThreadUpdate { + pub thread: Channel, +} + +impl WebSocketEvent for ThreadUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-delete +/// Not directly serialized, as the inner payload is a channel object +pub struct ThreadDelete { + pub thread: Channel, +} + +impl WebSocketEvent for ThreadDelete {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-list-sync +pub struct ThreadListSync { + pub guild_id: String, + pub channel_ids: Option>, + pub threads: Vec, + pub members: Vec +} + +impl WebSocketEvent for ThreadListSync {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-member-update +/// The inner payload is a thread member object with an extra field. +/// The extra field is a bit painful, because we can't just serialize a thread member object +pub struct ThreadMemberUpdate { + pub id: Option, + pub user_id: Option, + pub join_timestamp: Option, + pub flags: Option, + pub member: Option, + pub guild_id: String, +} + +impl ThreadMemberUpdate { + /// Convert self to a thread member, losing the added guild_id field + pub fn to_thread_member(&self) -> ThreadMember { + ThreadMember { id: self.id, user_id: self.user_id, join_timestamp: self.join_timestamp.clone(), flags: self.flags, member: self.member.clone() } + } +} + +impl WebSocketEvent for ThreadMemberUpdate {} + +#[derive(Debug, Default, Deserialize, Serialize)] +/// See https://discord.com/developers/docs/topics/gateway-events#thread-members-update +pub struct ThreadMembersUpdate { + pub id: String, + pub guild_id: String, + /// Capped at 50 + pub member_count: u8, + pub added_members: Option>, + pub removed_members: Option> +} + +impl WebSocketEvent for ThreadMembersUpdate {} + +#[derive(Debug, Deserialize, Serialize, Default)] +/// See https://discord.com/developers/docs/topics/gateway-events#guild-create +/// This one is particularly painful, it can be a Guild object with extra field or an unavailbile guild object +pub struct GuildCreate { + pub d: GuildCreateDataOption +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum GuildCreateDataOption { + UnavailableGuild(UnavailableGuild), + Guild(Guild) +} + +impl Default for GuildCreateDataOption { + fn default() -> Self { + GuildCreateDataOption::UnavailableGuild(UnavailableGuild::default()) + } +} +impl WebSocketEvent for GuildCreate {} + #[derive(Debug, Default, Deserialize, Serialize)] pub struct GatewayPayload { pub op: u8, diff --git a/src/gateway.rs b/src/gateway.rs index 2d6eb06..c79004f 100644 --- a/src/gateway.rs +++ b/src/gateway.rs @@ -167,7 +167,9 @@ impl Gateway { return; } - let gateway_payload: GatewayPayload = serde_json::from_str(msg.to_text().unwrap()).unwrap(); + let msg_string = msg.to_string(); + + let gateway_payload: GatewayPayload = serde_json::from_str(&msg_string).unwrap(); // See https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes match gateway_payload.op { @@ -189,17 +191,56 @@ impl Gateway { "AUTO_MODERATION_RULE_UPDATE" => {} "AUTO_MODERATION_RULE_DELETE" => {} "AUTO_MODERATION_ACTION_EXECUTION" => {} - "CHANNEL_CREATE" => {} - "CHANNEL_UPDATE" => {} - "CHANNEL_DELETE" => {} - "CHANNEL_PINS_UPDATE" => {} - "THREAD_CREATE" => {} - "THREAD_UPDATE" => {} - "THREAD_DELETE" => {} - "THREAD_LIST_SYNC" => {} - "THREAD_MEMBER_UPDATE" => {} - "THREAD_MEMBERS_UPDATE" => {} - "GUILD_CREATE" => {} + "CHANNEL_CREATE" => { + let channel: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = ChannelCreate {channel}; + self.events.lock().await.channel.create.update_data(new_data).await; + } + "CHANNEL_UPDATE" => { + let channel: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = ChannelUpdate {channel}; + self.events.lock().await.channel.update.update_data(new_data).await; + } + "CHANNEL_DELETE" => { + let channel: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = ChannelDelete {channel}; + self.events.lock().await.channel.delete.update_data(new_data).await; + } + "CHANNEL_PINS_UPDATE" => { + let new_data: ChannelPinsUpdate = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events.lock().await.channel.pins_update.update_data(new_data).await; + } + "THREAD_CREATE" => { + let thread: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = ThreadCreate {thread}; + self.events.lock().await.thread.create.update_data(new_data).await; + } + "THREAD_UPDATE" => { + let thread: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = ThreadUpdate {thread}; + self.events.lock().await.thread.update.update_data(new_data).await; + } + "THREAD_DELETE" => { + let thread: Channel = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + let new_data = ThreadDelete {thread}; + self.events.lock().await.thread.delete.update_data(new_data).await; + } + "THREAD_LIST_SYNC" => { + let new_data: ThreadListSync = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events.lock().await.thread.list_sync.update_data(new_data).await; + } + "THREAD_MEMBER_UPDATE" => { + let new_data: ThreadMemberUpdate = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events.lock().await.thread.member_update.update_data(new_data).await; + } + "THREAD_MEMBERS_UPDATE" => { + let new_data: ThreadMembersUpdate = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); + self.events.lock().await.thread.members_update.update_data(new_data).await; + } + "GUILD_CREATE" => { + let new_data: GuildCreate = serde_json::from_str(&msg_string).unwrap(); + self.events.lock().await.guild.create.update_data(new_data).await; + } "GUILD_UPDATE" => {} "GUILD_DELETE" => { let _new_data: UnavailableGuild = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); @@ -280,7 +321,7 @@ impl Gateway { "USER_UPDATE" => { let user: UserObject = serde_json::from_value(gateway_payload.d.unwrap()).unwrap(); let new_data = UserUpdate {user}; - self.events.lock().await.user.user_update.update_data(new_data).await; + self.events.lock().await.user.update.update_data(new_data).await; } "VOICE_STATE_UPDATE" => {} "VOICE_SERVER_UPDATE" => {} @@ -473,6 +514,9 @@ mod events { pub struct Events { pub message: Message, pub user: User, + pub channel: Channel, + pub thread: Thread, + pub guild: Guild, pub gateway_identify_payload: GatewayEvent, pub gateway_resume: GatewayEvent, } @@ -491,10 +535,53 @@ mod events { #[derive(Default, Debug)] pub struct User { - pub user_update: GatewayEvent, + pub update: GatewayEvent, pub presence_update: GatewayEvent, pub typing_start_event: GatewayEvent, } + + #[derive(Default, Debug)] + pub struct Channel { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub pins_update: GatewayEvent + } + + #[derive(Default, Debug)] + pub struct Thread { + pub create: GatewayEvent, + pub update: GatewayEvent, + pub delete: GatewayEvent, + pub list_sync: GatewayEvent, + pub member_update: GatewayEvent, + pub members_update: GatewayEvent, + } + + #[derive(Default, Debug)] + pub struct Guild { + pub create: GatewayEvent, + /*pub update: GatewayEvent, + pub delete: GatewayEvent, + pub audit_log_entry_create: GatewayEvent, + pub ban_add: GatewayEvent, + pub ban_remove: GatewayEvent, + pub emojis_update: GatewayEvent, + pub stickers_update: GatewayEvent, + pub integrations_update: GatewayEvent, + pub member_add: GatewayEvent, + pub member_remove: GatewayEvent, + pub member_update: GatewayEvent, + pub members_chunk: GatewayEvent, + pub role_create: GatewayEvent, + pub role_update: GatewayEvent, + pub role_delete: GatewayEvent, + pub role_scheduled_event_create: GatewayEvent, + pub role_scheduled_event_update: GatewayEvent, + pub role_scheduled_event_delete: GatewayEvent, + pub role_scheduled_event_user_add: GatewayEvent, + pub role_scheduled_event_user_remove: GatewayEvent,*/ + } } #[cfg(test)] From ca8f94c18e1b6b2c5be93410e78332b347833bdc Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 13 May 2023 21:42:43 +0200 Subject: [PATCH 02/22] create src/api/guilds/guilds.rs --- src/api/guilds/guilds.rs | 0 src/api/guilds/mod.rs | 3 +++ src/api/mod.rs | 2 ++ 3 files changed, 5 insertions(+) create mode 100644 src/api/guilds/guilds.rs create mode 100644 src/api/guilds/mod.rs diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/api/guilds/mod.rs b/src/api/guilds/mod.rs new file mode 100644 index 0000000..242a03e --- /dev/null +++ b/src/api/guilds/mod.rs @@ -0,0 +1,3 @@ +pub mod guilds; + +use guilds::*; diff --git a/src/api/mod.rs b/src/api/mod.rs index eb12765..37abc50 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,11 +1,13 @@ pub mod auth; pub mod channels; +pub mod guilds; pub mod policies; pub mod schemas; pub mod types; pub mod users; pub use channels::messages::*; +pub use guilds::*; pub use policies::instance::instance::*; pub use policies::instance::limits::*; pub use schemas::*; From 4e51d1251bb0f69a794715cfba1af9ee2c628d0a Mon Sep 17 00:00:00 2001 From: kozabrada123 <“kozabrada123@users.noreply.github.com”> Date: Sat, 13 May 2023 21:50:36 +0200 Subject: [PATCH 03/22] Add integrations --- src/api/types.rs | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/api/types.rs b/src/api/types.rs index 833691e..582917a 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -620,7 +620,7 @@ pub struct MessageActivity { pub party_id: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct Application { pub id: String, pub name: String, @@ -646,7 +646,7 @@ pub struct Application { pub role_connections_verification_url: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct Team { pub icon: Option, pub id: u64, @@ -655,7 +655,7 @@ pub struct Team { pub owner_user_id: u64, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct TeamMember { pub membership_state: u8, pub permissions: Vec, @@ -670,7 +670,7 @@ pub enum MembershipState { Accepted = 2, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct InstallParams { pub scopes: Vec, pub permissions: String, @@ -786,6 +786,35 @@ pub struct ThreadMember { pub member: Option, } +#[derive(Default, Debug, Deserialize, Serialize, Clone)] +/// See https://discord.com/developers/docs/resources/guild#integration-object-integration-structure +pub struct Integration { + pub id: String, + pub name: String, + #[serde(rename = "type")] + pub integration_type: String, + pub enabled: bool, + pub syncing: Option, + pub role_id: Option, + pub enabled_emoticons: Option, + pub expire_behaviour: Option, + pub expire_grace_period: Option, + pub user: Option, + pub account: IntegrationAccount, + pub synced_at: Option>, + pub subscriber_count: Option, + pub revoked: Option, + pub application: Option, + pub scopes: Option> +} + +#[derive(Default, Debug, Deserialize, Serialize, Clone)] +/// See https://discord.com/developers/docs/resources/guild#integration-account-object-integration-account-structure +pub struct IntegrationAccount { + pub id: String, + pub name: String +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct DefaultReaction { pub emoji_id: Option, From a93962b667a25525bb90a6344292f5d7573626ef Mon Sep 17 00:00:00 2001 From: kozabrada123 <“kozabrada123@users.noreply.github.com”> Date: Sat, 13 May 2023 22:01:04 +0200 Subject: [PATCH 04/22] Add Stage Instance and Voice State --- src/api/types.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/api/types.rs b/src/api/types.rs index 582917a..b3f7081 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -815,6 +815,36 @@ pub struct IntegrationAccount { pub name: String } +#[derive(Default, Debug, Deserialize, Serialize, Clone)] +/// See https://discord.com/developers/docs/resources/voice#voice-state-object +pub struct VoiceStateObject { + pub guild_id: Option, + pub channel_id: Option, + pub user_id: String, + pub member: Option, + pub session_id: String, + pub deaf: bool, + pub mute: bool, + pub self_deaf: bool, + pub self_mute: bool, + pub self_stream: Option, + pub self_video: bool, + pub suppress: bool, + pub request_to_speak_timestamp: DateTime +} + +#[derive(Default, Debug, Deserialize, Serialize, Clone)] +/// See https://discord.com/developers/docs/resources/stage-instance#stage-instance-object +pub struct StageInstance { + pub id: String, + pub guild_id: String, + pub channel_id: String, + pub topic: String, + pub privacy_level: u8, + pub discoverable_disabled: bool, + pub guild_scheduled_event_id: Option +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct DefaultReaction { pub emoji_id: Option, From c436fdb8573e0369775e1e5fc9d9b95e67644e85 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 13 May 2023 22:06:44 +0200 Subject: [PATCH 05/22] Remove panic on send_request error --- src/limit.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/limit.rs b/src/limit.rs index 82f5732..9de72af 100644 --- a/src/limit.rs +++ b/src/limit.rs @@ -78,7 +78,11 @@ impl LimitedRequester { let result = self.http.execute(built_request).await; let response = match result { Ok(is_response) => is_response, - Err(e) => panic!("An error occured while processing the response: {}", e), + Err(e) => { + return Err(InstanceServerError::ReceivedErrorCodeError { + error_code: e.to_string(), + }) + } }; self.update_limits( &response, @@ -86,7 +90,13 @@ impl LimitedRequester { instance_rate_limits, user_rate_limits, ); - Ok(response) + if !response.status().is_success() { + Err(InstanceServerError::ReceivedErrorCodeError { + error_code: response.status().as_str().to_string(), + }) + } else { + Ok(response) + } } else { self.requests.push_back(TypedRequest { request, From 5031db6547e0411d50feb1a4c2720b2fd92e2607 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 13 May 2023 22:10:46 +0200 Subject: [PATCH 06/22] Update docs, remove panic in favor of Err Result --- src/limit.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/limit.rs b/src/limit.rs index 9de72af..07dec0f 100644 --- a/src/limit.rs +++ b/src/limit.rs @@ -57,7 +57,8 @@ impl LimitedRequester { ## Errors - This method will panic, if: + This method will error, if: + - The request does not return a success status code (200-299) - The supplied [`RequestBuilder`](reqwest::RequestBuilder) contains invalid or incomplete information - There has been an error with processing (unwrapping) the [`Response`](`reqwest::Response`) @@ -72,9 +73,15 @@ impl LimitedRequester { user_rate_limits: &mut Limits, ) -> Result { if self.can_send_request(limit_type, instance_rate_limits, user_rate_limits) { - let built_request = request - .build() - .unwrap_or_else(|e| panic!("Error while building the Request for sending: {}", e)); + let built_request = match request.build() { + Ok(request) => request, + Err(e) => { + return Err(InstanceServerError::RequestErrorError { + url: "".to_string(), + error: e.to_string(), + }) + } + }; let result = self.http.execute(built_request).await; let response = match result { Ok(is_response) => is_response, From e0a45edb3c9eb70aabddf80eca7fc68d00f2e5cf Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 13 May 2023 22:11:06 +0200 Subject: [PATCH 07/22] Add GuildCreateSchema as per Spacebar Docs --- src/api/schemas.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/api/schemas.rs b/src/api/schemas.rs index 670a828..6c021b6 100644 --- a/src/api/schemas.rs +++ b/src/api/schemas.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::errors::FieldFormatError; -use super::Embed; +use super::{Channel, Embed}; /** A struct that represents a well-formed email address. @@ -287,6 +287,18 @@ impl MessageSendSchema { } } +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct GuildCreateSchema { + name: Option, + region: Option, + icon: Option, + channels: Option>, + guild_template_code: Option, + system_channel_id: Option, + rules_channel_id: Option, +} + // I know that some of these tests are... really really basic and unneccessary, but sometimes, I // just feel like writing tests, so there you go :) -@bitfl0wer #[cfg(test)] From 1934622e4c9454c7492dda419f904b48a82d3a08 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 13 May 2023 22:14:53 +0200 Subject: [PATCH 08/22] Implement Guild::create() --- src/api/guilds/guilds.rs | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index e69de29..d3fd17a 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -0,0 +1,42 @@ +use serde_json::to_string; + +use crate::api::schemas; +use crate::api::types; + +impl<'a> types::Guild { + pub async fn create( + user: &mut types::User<'a>, + instance: &mut crate::instance::Instance, + guild_create_schema: &schemas::GuildCreateSchema, + ) -> Result { + let url = format!("{}/guilds/", instance.urls.get_api().to_string()); + let limits_user = user.limits.get_as_mut(); + let limits_instance = instance.limits.get_as_mut(); + let request = reqwest::Client::new() + .post(url.clone()) + .bearer_auth(user.token.clone()) + .body(to_string(guild_create_schema).unwrap()); + let mut requester = crate::limit::LimitedRequester::new().await; + let result = match requester + .send_request( + request, + crate::api::limits::LimitType::Guild, + limits_instance, + limits_user, + ) + .await + { + Ok(result) => result, + Err(e) => return Err(e), + }; + return Ok(match result.text().await { + Ok(string) => string, + Err(e) => { + return Err(crate::errors::InstanceServerError::RequestErrorError { + url: url.to_string(), + error: e.to_string(), + }) + } + }); + } +} From ea454228ac0d4e1e4c6352ed165b6ed9ce32660c Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 13 May 2023 22:55:17 +0200 Subject: [PATCH 09/22] Add documentation --- src/api/guilds/guilds.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index d3fd17a..b067b05 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -4,6 +4,34 @@ use crate::api::schemas; use crate::api::types; impl<'a> types::Guild { + /// Creates a new guild with the given parameters. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to the user creating the guild. + /// * `instance` - A mutable reference to the instance where the guild will be created. + /// * `guild_create_schema` - A reference to the schema containing the guild creation parameters. + /// + /// # Returns + /// + /// A `Result` containing the ID of the newly created guild, or an error if the request fails. + /// + /// # Errors + /// + /// Returns an `InstanceServerError` if the request fails. + /// + /// # Examples + /// + /// ```rust + /// let guild_create_schema = chorus::api::schemas::GuildCreateSchema::new(insert args here); + /// + /// let result = Guild::create(&mut user, &mut instance, &guild_create_schema).await; + /// + /// match result { + /// Ok(guild_id) => println!("Created guild with ID {}", guild_id), + /// Err(e) => println!("Failed to create guild: {}", e), + /// } + /// ``` pub async fn create( user: &mut types::User<'a>, instance: &mut crate::instance::Instance, From 3fca30db350b8b58d1d71b4bbbd9598b01dd65d6 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 13 May 2023 23:15:28 +0200 Subject: [PATCH 10/22] start implementing get() --- src/api/guilds/guilds.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index b067b05..f56acd0 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -35,7 +35,7 @@ impl<'a> types::Guild { pub async fn create( user: &mut types::User<'a>, instance: &mut crate::instance::Instance, - guild_create_schema: &schemas::GuildCreateSchema, + guild_create_schema: schemas::GuildCreateSchema, ) -> Result { let url = format!("{}/guilds/", instance.urls.get_api().to_string()); let limits_user = user.limits.get_as_mut(); @@ -43,7 +43,7 @@ impl<'a> types::Guild { let request = reqwest::Client::new() .post(url.clone()) .bearer_auth(user.token.clone()) - .body(to_string(guild_create_schema).unwrap()); + .body(to_string(&guild_create_schema).unwrap()); let mut requester = crate::limit::LimitedRequester::new().await; let result = match requester .send_request( @@ -67,4 +67,16 @@ impl<'a> types::Guild { } }); } + pub async fn get( + user: &mut types::User<'a>, + instance: &mut crate::instance::Instance, + id: String, + ) { + let url = format!("{}/guilds/{}/", instance.urls.get_api().to_string(), id); + let limits_user = user.limits.get_as_mut(); + let limits_instance = instance.limits.get_as_mut(); + let request = reqwest::Client::new() + .get(url.clone()) + .bearer_auth(user.token.clone()); + } } From 51ecf888b8b38ef255796313df907d8a9bb2c5c9 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 13 May 2023 23:45:49 +0200 Subject: [PATCH 11/22] remove get() --- src/api/guilds/guilds.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index f56acd0..7e1bb94 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -67,16 +67,4 @@ impl<'a> types::Guild { } }); } - pub async fn get( - user: &mut types::User<'a>, - instance: &mut crate::instance::Instance, - id: String, - ) { - let url = format!("{}/guilds/{}/", instance.urls.get_api().to_string(), id); - let limits_user = user.limits.get_as_mut(); - let limits_instance = instance.limits.get_as_mut(); - let request = reqwest::Client::new() - .get(url.clone()) - .bearer_auth(user.token.clone()); - } } From 2072ae11a57330467395feca07790e4a9ddfa862 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 12:40:31 +0200 Subject: [PATCH 12/22] Add full Guild type --- src/api/types.rs | 177 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 151 insertions(+), 26 deletions(-) diff --git a/src/api/types.rs b/src/api/types.rs index b3f7081..ae3a8e7 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -137,7 +137,7 @@ pub struct Error { #[derive(Serialize, Deserialize, Debug, Default)] pub struct UnavailableGuild { id: String, - unavailable: bool + unavailable: bool, } /// See https://discord.com/developers/docs/resources/guild @@ -149,41 +149,71 @@ pub struct Guild { pub icon_hash: Option, pub splash: Option, pub discovery_splash: Option, - pub owner: Option, - pub owner_id: String, + pub owner: Option, + pub owner_id: Option, pub permissions: Option, pub afk_channel_id: Option, - pub afk_timeout: u8, + pub afk_timeout: Option, pub widget_enabled: Option, pub widget_channel_id: Option, - pub verification_level: u8, - pub default_message_notifications: u8, - pub explicit_content_filter: u8, + pub widget_channel: Option, + pub verification_level: Option, + pub default_message_notifications: Option, + pub explicit_content_filter: Option, pub roles: Vec, pub emojis: Vec, pub features: Vec, - pub mfa_level: u8, pub application_id: Option, pub system_channel_id: Option, - pub system_channel_flags: u8, + pub system_channel_flags: Option, pub rules_channel_id: Option, + pub rules_channel: Option, pub max_presences: Option, pub max_members: Option, pub vanity_url_code: Option, pub description: Option, pub banner: Option, - pub premium_tier: u8, + pub premium_tier: Option, pub premium_subscription_count: Option, - pub preferred_locale: String, + pub preferred_locale: Option, pub public_updates_channel_id: Option, + pub public_updates_channel: Option, pub max_video_channel_users: Option, pub max_stage_video_channel_users: Option, pub approximate_member_count: Option, pub approximate_presence_count: Option, + pub member_count: Option, + pub presence_count: Option, pub welcome_screen: Option, pub nsfw_level: u8, + pub nsfw: bool, pub stickers: Option>, - pub premium_progress_bar_enabled: bool + pub premium_progress_bar_enabled: Option, + pub joined_at: String, + pub afk_channel: Option, + pub bans: Option>, + pub primary_category_id: Option, + pub large: Option, + pub channels: Option>, + pub template_id: Option, + pub template: Option, + pub invites: Option>, + pub voice_states: Option>, + pub webhooks: Option>, + pub mfa_level: Option, + pub region: Option, + pub unavailable: bool, + pub parent: Option, +} + +/// See https://docs.spacebar.chat/routes/#get-/guilds/-guild_id-/bans/-user- +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildBan { + pub id: String, + pub user_id: String, + pub guild_id: String, + pub executor_id: String, + pub reason: Option, } /// See https://discord.com/developers/docs/topics/gateway-events#guild-create-guild-create-extra-fields @@ -259,7 +289,7 @@ impl GuildCreateGuild { #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct WelcomeScreenObject { pub description: Option, - pub welcome_channels: Vec + pub welcome_channels: Vec, } #[derive(Serialize, Deserialize, Debug, Default, Clone)] @@ -267,7 +297,7 @@ pub struct WelcomeScreenChannel { pub channel_id: String, pub description: String, pub emoji_id: Option, - pub emoji_name: Option + pub emoji_name: Option, } #[derive(Serialize, Deserialize, Debug, Default, Clone)] @@ -805,14 +835,14 @@ pub struct Integration { pub subscriber_count: Option, pub revoked: Option, pub application: Option, - pub scopes: Option> + pub scopes: Option>, } #[derive(Default, Debug, Deserialize, Serialize, Clone)] /// See https://discord.com/developers/docs/resources/guild#integration-account-object-integration-account-structure pub struct IntegrationAccount { pub id: String, - pub name: String + pub name: String, } #[derive(Default, Debug, Deserialize, Serialize, Clone)] @@ -830,7 +860,7 @@ pub struct VoiceStateObject { pub self_stream: Option, pub self_video: bool, pub suppress: bool, - pub request_to_speak_timestamp: DateTime + pub request_to_speak_timestamp: DateTime, } #[derive(Default, Debug, Deserialize, Serialize, Clone)] @@ -842,7 +872,7 @@ pub struct StageInstance { pub topic: String, pub privacy_level: u8, pub discoverable_disabled: bool, - pub guild_scheduled_event_id: Option + pub guild_scheduled_event_id: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -933,7 +963,7 @@ pub struct PresenceUpdate { pub guild_id: String, pub status: String, pub activities: Vec, - pub client_status: ClientStatusObject + pub client_status: ClientStatusObject, } #[derive(Debug, Deserialize, Serialize, Default, Clone)] @@ -941,7 +971,7 @@ pub struct PresenceUpdate { pub struct ClientStatusObject { pub desktop: Option, pub mobile: Option, - pub web: Option + pub web: Option, } impl WebSocketEvent for PresenceUpdate {} @@ -1080,7 +1110,7 @@ impl WebSocketEvent for GatewayHeartbeatAck {} pub struct ChannelPinsUpdate { pub guild_id: Option, pub channel_id: String, - pub last_pin_timestamp: Option> + pub last_pin_timestamp: Option>, } impl WebSocketEvent for ChannelPinsUpdate {} @@ -1172,7 +1202,7 @@ pub struct ThreadListSync { pub guild_id: String, pub channel_ids: Option>, pub threads: Vec, - pub members: Vec + pub members: Vec, } impl WebSocketEvent for ThreadListSync {} @@ -1193,7 +1223,13 @@ pub struct ThreadMemberUpdate { impl ThreadMemberUpdate { /// Convert self to a thread member, losing the added guild_id field pub fn to_thread_member(&self) -> ThreadMember { - ThreadMember { id: self.id, user_id: self.user_id, join_timestamp: self.join_timestamp.clone(), flags: self.flags, member: self.member.clone() } + ThreadMember { + id: self.id, + user_id: self.user_id, + join_timestamp: self.join_timestamp.clone(), + flags: self.flags, + member: self.member.clone(), + } } } @@ -1207,7 +1243,7 @@ pub struct ThreadMembersUpdate { /// Capped at 50 pub member_count: u8, pub added_members: Option>, - pub removed_members: Option> + pub removed_members: Option>, } impl WebSocketEvent for ThreadMembersUpdate {} @@ -1216,13 +1252,13 @@ impl WebSocketEvent for ThreadMembersUpdate {} /// See https://discord.com/developers/docs/topics/gateway-events#guild-create /// This one is particularly painful, it can be a Guild object with extra field or an unavailbile guild object pub struct GuildCreate { - pub d: GuildCreateDataOption + pub d: GuildCreateDataOption, } #[derive(Debug, Deserialize, Serialize)] pub enum GuildCreateDataOption { UnavailableGuild(UnavailableGuild), - Guild(Guild) + Guild(Guild), } impl Default for GuildCreateDataOption { @@ -1317,6 +1353,7 @@ impl PartialDiscordFileAttachment { proxy_url: self.proxy_url, height: self.height, width: self.width, + ephemeral: self.ephemeral, duration_secs: self.duration_secs, waveform: self.waveform, @@ -1370,3 +1407,91 @@ pub enum AllowedMentionType { pub struct Token { pub token: String, } + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-template +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildTemplate { + pub code: String, + pub name: String, + pub description: Option, + pub usage_count: Option, + pub creator_id: String, + pub creator: UserObject, + pub created_at: DateTime, + pub updated_at: DateTime, + pub source_guild_id: String, + pub source_guild: Vec, // Unsure how a {recursive: Guild} looks like, might be a Vec? + pub serialized_source_guild: Vec, + id: String, +} + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-invite +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildInvite { + pub code: String, + pub temporary: Option, + pub uses: Option, + pub max_uses: Option, + pub max_age: Option, + pub created_at: DateTime, + pub expires_at: Option>, + pub guild_id: String, + pub guild: Option, + pub channel_id: String, + pub channel: Option, + pub inviter_id: Option, + pub inviter: Option, + pub target_user_id: Option, + pub target_user: Option, + pub target_user_type: Option, + pub vanity_url: Option, +} + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-voicestate +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct VoiceState { + pub guild_id: String, + pub guild: Option, + pub channel_id: String, + pub channel: Option, + pub user_id: String, + pub user: Option, + pub member: Option, + pub session_id: String, + pub token: String, + pub deaf: bool, + pub mute: bool, + pub self_deaf: bool, + pub self_mute: bool, + pub self_stream: Option, + pub self_video: bool, + pub suppress: bool, + pub request_to_speak_timestamp: Option>, + pub id: String, +} + +/// See https://docs.spacebar.chat/routes/#cmp--schemas-webhook +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct Webhook { + #[serde(rename = "type")] + pub webhook_type: i32, + pub name: String, + pub avatar: String, + pub token: String, + pub guild_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub guild: Option, + pub channel_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + pub application_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub application: Option, + pub user_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option, + pub source_guild_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub source_guild: Option, + pub id: String, +} From 9b1a7f9bd8c810112d0047923e5dc4b8ebc20f4b Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 12:55:19 +0200 Subject: [PATCH 13/22] Remove duplicate VoiceState --- src/api/types.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/api/types.rs b/src/api/types.rs index ae3a8e7..c87786b 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -845,24 +845,6 @@ pub struct IntegrationAccount { pub name: String, } -#[derive(Default, Debug, Deserialize, Serialize, Clone)] -/// See https://discord.com/developers/docs/resources/voice#voice-state-object -pub struct VoiceStateObject { - pub guild_id: Option, - pub channel_id: Option, - pub user_id: String, - pub member: Option, - pub session_id: String, - pub deaf: bool, - pub mute: bool, - pub self_deaf: bool, - pub self_mute: bool, - pub self_stream: Option, - pub self_video: bool, - pub suppress: bool, - pub request_to_speak_timestamp: DateTime, -} - #[derive(Default, Debug, Deserialize, Serialize, Clone)] /// See https://discord.com/developers/docs/resources/stage-instance#stage-instance-object pub struct StageInstance { From 598ad093a160f40607b6772fa49663d8de30d5eb Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 13:07:46 +0200 Subject: [PATCH 14/22] Improve error handling on request sending --- src/errors.rs | 2 ++ src/limit.rs | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 76d16bd..e50a931 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -19,6 +19,8 @@ custom_error! { InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}", RateLimited = "Ratelimited.", MultipartCreationError{error: String} = "Got an error whilst creating the form: {}", + TokenExpired = "Token expired, invalid or not found.", + NoPermission = "You do not have the permissions needed to perform this action.", } custom_error! { diff --git a/src/limit.rs b/src/limit.rs index 07dec0f..7212e7c 100644 --- a/src/limit.rs +++ b/src/limit.rs @@ -98,9 +98,15 @@ impl LimitedRequester { user_rate_limits, ); if !response.status().is_success() { - Err(InstanceServerError::ReceivedErrorCodeError { - error_code: response.status().as_str().to_string(), - }) + match response.status().as_u16() { + 401 => return Err(InstanceServerError::TokenExpired), + 403 => return Err(InstanceServerError::TokenExpired), + _ => { + return Err(InstanceServerError::ReceivedErrorCodeError { + error_code: response.status().as_str().to_string(), + }) + } + } } else { Ok(response) } From 4b4adbe1cc205c28785c18f98e4612429f45a2bb Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 13:11:50 +0200 Subject: [PATCH 15/22] impl ToString for LimitType --- src/api/policies/instance/limits.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/api/policies/instance/limits.rs b/src/api/policies/instance/limits.rs index 2818135..1c96883 100644 --- a/src/api/policies/instance/limits.rs +++ b/src/api/policies/instance/limits.rs @@ -1,5 +1,5 @@ pub mod limits { - use std::{collections::HashMap}; + use std::collections::HashMap; use reqwest::Client; use serde::{Deserialize, Serialize}; @@ -20,6 +20,23 @@ pub mod limits { Webhook, } + impl ToString for LimitType { + fn to_string(&self) -> String { + match self { + LimitType::AuthRegister => "AuthRegister".to_string(), + LimitType::AuthLogin => "AuthLogin".to_string(), + LimitType::AbsoluteMessage => "AbsoluteMessage".to_string(), + LimitType::AbsoluteRegister => "AbsoluteRegister".to_string(), + LimitType::Global => "Global".to_string(), + LimitType::Ip => "Ip".to_string(), + LimitType::Channel => "Channel".to_string(), + LimitType::Error => "Error".to_string(), + LimitType::Guild => "Guild".to_string(), + LimitType::Webhook => "Webhook".to_string(), + } + } + } + #[derive(Debug, Deserialize, Serialize)] #[allow(non_snake_case)] pub struct User { From d797a1000966e2496bd5aa630f18fe76fc260374 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 13:12:02 +0200 Subject: [PATCH 16/22] Add RateLimit information --- src/errors.rs | 2 +- src/limit.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index e50a931..599b262 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,7 +17,7 @@ custom_error! { ReceivedErrorCodeError{error_code:String} = "Received the following error code while requesting from the route: {error_code}", CantGetInfoError{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}", InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}", - RateLimited = "Ratelimited.", + RateLimited{bucket:String} = "Ratelimited on Bucket {bucket}", MultipartCreationError{error: String} = "Got an error whilst creating the form: {}", TokenExpired = "Token expired, invalid or not found.", NoPermission = "You do not have the permissions needed to perform this action.", diff --git a/src/limit.rs b/src/limit.rs index 7212e7c..cc058b6 100644 --- a/src/limit.rs +++ b/src/limit.rs @@ -115,7 +115,9 @@ impl LimitedRequester { request, limit_type, }); - Err(InstanceServerError::RateLimited) + Err(InstanceServerError::RateLimited { + bucket: limit_type.to_string(), + }) } } From 833c3733d7db7a91646a296caab53dbe0d170901 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 14:16:21 +0200 Subject: [PATCH 17/22] Add guild delete route --- src/api/guilds/guilds.rs | 56 ++++++++++++++++++++++++++++++++++++++++ src/errors.rs | 1 + 2 files changed, 57 insertions(+) diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index 7e1bb94..bd08075 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -2,6 +2,7 @@ use serde_json::to_string; use crate::api::schemas; use crate::api::types; +use crate::errors::InstanceServerError; impl<'a> types::Guild { /// Creates a new guild with the given parameters. @@ -67,4 +68,59 @@ impl<'a> types::Guild { } }); } + + /// Deletes a guild. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a `User` instance. + /// * `instance` - A mutable reference to an `Instance` instance. + /// * `guild_id` - A `String` representing the ID of the guild to delete. + /// + /// # Returns + /// + /// An `Option` containing an `InstanceServerError` if an error occurred during the request, otherwise `None`. + /// + /// # Example + /// + /// ```rust + /// let mut user = User::new(); + /// let mut instance = Instance::new(); + /// let guild_id = String::from("1234567890"); + /// + /// match Guild::delete(&mut user, &mut instance, guild_id) { + /// Some(e) => println!("Error deleting guild: {:?}", e), + /// None => println!("Guild deleted successfully"), + /// } + /// ``` + pub async fn delete( + user: &mut types::User<'a>, + instance: &mut crate::instance::Instance, + guild_id: String, + ) -> Option { + let url = format!( + "{}/guilds/{}/delete/", + instance.urls.get_api().to_string(), + guild_id + ); + let limits_user = user.limits.get_as_mut(); + let limits_instance = instance.limits.get_as_mut(); + let request = reqwest::Client::new() + .post(url.clone()) + .bearer_auth(user.token.clone()); + let mut requester = crate::limit::LimitedRequester::new().await; + let result = requester + .send_request( + request, + crate::api::limits::LimitType::Guild, + limits_instance, + limits_user, + ) + .await; + if result.is_err() { + Some(result.err().unwrap()) + } else { + None + } + } } diff --git a/src/errors.rs b/src/errors.rs index 599b262..d449559 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -21,6 +21,7 @@ custom_error! { MultipartCreationError{error: String} = "Got an error whilst creating the form: {}", TokenExpired = "Token expired, invalid or not found.", NoPermission = "You do not have the permissions needed to perform this action.", + NotFound{error: String} = "The provided resource hasn't been found: {}", } custom_error! { From 120cdfd14fbc9964dda5074c4091aa4a44627a4d Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 22:45:18 +0200 Subject: [PATCH 18/22] Change function signatures, add tests --- src/api/guilds/guilds.rs | 81 ++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index bd08075..9bc6c78 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -1,3 +1,4 @@ +use serde_json::from_str; use serde_json::to_string; use crate::api::schemas; @@ -35,12 +36,12 @@ impl<'a> types::Guild { /// ``` pub async fn create( user: &mut types::User<'a>, - instance: &mut crate::instance::Instance, + url_api: &str, guild_create_schema: schemas::GuildCreateSchema, ) -> Result { - let url = format!("{}/guilds/", instance.urls.get_api().to_string()); + let url = format!("{}/guilds/", url_api); let limits_user = user.limits.get_as_mut(); - let limits_instance = instance.limits.get_as_mut(); + let limits_instance = &mut user.belongs_to.limits; let request = reqwest::Client::new() .post(url.clone()) .bearer_auth(user.token.clone()) @@ -58,15 +59,8 @@ impl<'a> types::Guild { Ok(result) => result, Err(e) => return Err(e), }; - return Ok(match result.text().await { - Ok(string) => string, - Err(e) => { - return Err(crate::errors::InstanceServerError::RequestErrorError { - url: url.to_string(), - error: e.to_string(), - }) - } - }); + let id: types::GuildCreateResponse = from_str(&result.text().await.unwrap()).unwrap(); + Ok(id.id) } /// Deletes a guild. @@ -95,16 +89,12 @@ impl<'a> types::Guild { /// ``` pub async fn delete( user: &mut types::User<'a>, - instance: &mut crate::instance::Instance, + url_api: &str, guild_id: String, ) -> Option { - let url = format!( - "{}/guilds/{}/delete/", - instance.urls.get_api().to_string(), - guild_id - ); + let url = format!("{}/guilds/{}/delete/", url_api, guild_id); let limits_user = user.limits.get_as_mut(); - let limits_instance = instance.limits.get_as_mut(); + let limits_instance = &mut user.belongs_to.limits; let request = reqwest::Client::new() .post(url.clone()) .bearer_auth(user.token.clone()); @@ -124,3 +114,56 @@ impl<'a> types::Guild { } } } + +#[cfg(test)] +mod test { + use crate::api::schemas; + use crate::api::types; + use crate::instance::Instance; + + #[tokio::test] + async fn guild_creation_deletion() { + let mut instance = Instance::new( + crate::URLBundle { + api: "http://localhost:3001/api".to_string(), + wss: "ws://localhost:3001/".to_string(), + cdn: "http://localhost:3001".to_string(), + }, + crate::limit::LimitedRequester::new().await, + ) + .await + .unwrap(); + let login_schema: schemas::LoginSchema = schemas::LoginSchema::new( + schemas::AuthUsername::new("user@test.xyz".to_string()).unwrap(), + "transrights".to_string(), + None, + None, + None, + None, + ) + .unwrap(); + let mut user = instance.login_account(&login_schema).await.unwrap(); + + let guild_create_schema = schemas::GuildCreateSchema { + name: Some("test".to_string()), + region: None, + icon: None, + channels: None, + guild_template_code: None, + system_channel_id: None, + rules_channel_id: None, + }; + + let guild = + types::Guild::create(&mut user, "http://localhost:3001/api", guild_create_schema) + .await + .unwrap(); + + println!("{}", guild); + + match types::Guild::delete(&mut user, "http://localhost:3001/api", guild).await { + None => assert!(true), + Some(_) => assert!(false), + } + } +} From fbfd0e627f84493a950795a96828c85d5232db3f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 22:45:31 +0200 Subject: [PATCH 19/22] Remove unused import --- src/api/channels/messages.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/channels/messages.rs b/src/api/channels/messages.rs index d05f640..ffa3f06 100644 --- a/src/api/channels/messages.rs +++ b/src/api/channels/messages.rs @@ -5,7 +5,6 @@ pub mod messages { use serde_json::to_string; use crate::api::types::{Message, PartialDiscordFileAttachment, User}; - use crate::errors::InstanceServerError; use crate::limit::LimitedRequester; impl Message { From b4cc8fd64b0dbadd7d6f4249e4eef5417bbc0bc6 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 22:45:42 +0200 Subject: [PATCH 20/22] add type guildcreateresponse --- src/api/types.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/api/types.rs b/src/api/types.rs index c87786b..bfb6861 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -1477,3 +1477,8 @@ pub struct Webhook { pub source_guild: Option, pub id: String, } + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildCreateResponse { + pub id: String, +} From e552bdb352480cf4236a27e22560bad29324fcf1 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 22:45:55 +0200 Subject: [PATCH 21/22] make fields on GuildCreateSchema pub --- src/api/schemas.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/api/schemas.rs b/src/api/schemas.rs index 6c021b6..1fd0e76 100644 --- a/src/api/schemas.rs +++ b/src/api/schemas.rs @@ -290,13 +290,13 @@ impl MessageSendSchema { #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub struct GuildCreateSchema { - name: Option, - region: Option, - icon: Option, - channels: Option>, - guild_template_code: Option, - system_channel_id: Option, - rules_channel_id: Option, + pub name: Option, + pub region: Option, + pub icon: Option, + pub channels: Option>, + pub guild_template_code: Option, + pub system_channel_id: Option, + pub rules_channel_id: Option, } // I know that some of these tests are... really really basic and unneccessary, but sometimes, I From aaa16ef532534941c646ce61e039f3e3bbc70e92 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 14 May 2023 22:47:34 +0200 Subject: [PATCH 22/22] remove accidental doctests --- src/api/guilds/guilds.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs index 9bc6c78..23249b9 100644 --- a/src/api/guilds/guilds.rs +++ b/src/api/guilds/guilds.rs @@ -24,7 +24,7 @@ impl<'a> types::Guild { /// /// # Examples /// - /// ```rust + /// ```rs /// let guild_create_schema = chorus::api::schemas::GuildCreateSchema::new(insert args here); /// /// let result = Guild::create(&mut user, &mut instance, &guild_create_schema).await; @@ -77,7 +77,7 @@ impl<'a> types::Guild { /// /// # Example /// - /// ```rust + /// ```rs /// let mut user = User::new(); /// let mut instance = Instance::new(); /// let guild_id = String::from("1234567890");