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 { diff --git a/src/api/guilds/guilds.rs b/src/api/guilds/guilds.rs new file mode 100644 index 0000000..23249b9 --- /dev/null +++ b/src/api/guilds/guilds.rs @@ -0,0 +1,169 @@ +use serde_json::from_str; +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. + /// + /// # 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 + /// + /// ```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; + /// + /// 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>, + url_api: &str, + guild_create_schema: schemas::GuildCreateSchema, + ) -> Result { + let url = format!("{}/guilds/", url_api); + let limits_user = user.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()) + .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), + }; + let id: types::GuildCreateResponse = from_str(&result.text().await.unwrap()).unwrap(); + Ok(id.id) + } + + /// 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 + /// + /// ```rs + /// 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>, + url_api: &str, + guild_id: String, + ) -> Option { + let url = format!("{}/guilds/{}/delete/", url_api, guild_id); + let limits_user = user.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()); + 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 + } + } +} + +#[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), + } + } +} 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::*; 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 { diff --git a/src/api/schemas.rs b/src/api/schemas.rs index 670a828..1fd0e76 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 { + 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 // just feel like writing tests, so there you go :) -@bitfl0wer #[cfg(test)] diff --git a/src/api/types.rs b/src/api/types.rs index e138eec..bfb6861 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}; @@ -136,10 +137,187 @@ pub struct Error { #[derive(Serialize, Deserialize, Debug, Default)] pub struct UnavailableGuild { id: String, - unavailable: bool + 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: Option, + pub permissions: Option, + pub afk_channel_id: Option, + pub afk_timeout: Option, + pub widget_enabled: Option, + pub widget_channel_id: Option, + 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 application_id: Option, + pub system_channel_id: Option, + 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: Option, + pub premium_subscription_count: Option, + 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: 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 +/// 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 +625,242 @@ 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, +#[derive(Debug, Deserialize, Serialize, Clone)] +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, +#[derive(Debug, Deserialize, Serialize, Clone)] +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, +#[derive(Debug, Deserialize, Serialize, Clone)] +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, +#[derive(Debug, Serialize, Deserialize, Clone)] +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(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(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, + pub emoji_name: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -657,44 +876,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 +938,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 +978,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 +998,7 @@ struct ActivityAssets { small_text: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] struct ActivitySecrets { join: Option, spectate: Option, @@ -777,7 +1006,7 @@ struct ActivitySecrets { match_string: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] struct ActivityButton { label: String, url: String, @@ -863,7 +1092,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 {} @@ -895,6 +1124,132 @@ 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, @@ -980,6 +1335,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, @@ -1033,3 +1389,96 @@ 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, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GuildCreateResponse { + pub id: String, +} diff --git a/src/errors.rs b/src/errors.rs index 76d16bd..d449559 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,8 +17,11 @@ 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.", + NotFound{error: String} = "The provided resource hasn't been found: {}", } custom_error! { 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)] diff --git a/src/limit.rs b/src/limit.rs index 82f5732..cc058b6 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,13 +73,23 @@ 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, - 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,13 +97,27 @@ impl LimitedRequester { instance_rate_limits, user_rate_limits, ); - Ok(response) + if !response.status().is_success() { + 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) + } } else { self.requests.push_back(TypedRequest { request, limit_type, }); - Err(InstanceServerError::RateLimited) + Err(InstanceServerError::RateLimited { + bucket: limit_type.to_string(), + }) } }