diff --git a/examples/cache-optimization/models/channel.rs b/examples/cache-optimization/models/channel.rs index a1e4ab5dae3..a8cddeddc78 100644 --- a/examples/cache-optimization/models/channel.rs +++ b/examples/cache-optimization/models/channel.rs @@ -60,4 +60,8 @@ impl CacheableChannel for MinimalCachedChannel { fn set_last_pin_timestamp(&mut self, _timestamp: Option) { // We don't store this information, so this is a no-op } + + fn set_status(&mut self, _status: Option) { + // We don't store this information, so this is a no-op + } } diff --git a/twilight-cache-inmemory/src/event/channel.rs b/twilight-cache-inmemory/src/event/channel.rs index fac70866711..232f0a10150 100644 --- a/twilight-cache-inmemory/src/event/channel.rs +++ b/twilight-cache-inmemory/src/event/channel.rs @@ -1,4 +1,5 @@ use crate::{traits::CacheableChannel, CacheableModels, InMemoryCache, ResourceType, UpdateCache}; +use twilight_model::gateway::payload::incoming::VoiceChannelStatusUpdate; use twilight_model::{ channel::Channel, gateway::payload::incoming::{ChannelCreate, ChannelDelete, ChannelPinsUpdate, ChannelUpdate}, @@ -83,6 +84,18 @@ impl UpdateCache for ChannelUpdate { } } +impl UpdateCache for VoiceChannelStatusUpdate { + fn update(&self, cache: &InMemoryCache) { + if !cache.wants(ResourceType::CHANNEL) { + return; + } + + if let Some(mut channel) = cache.channels.get_mut(&self.id) { + channel.set_status(self.status.clone()); + } + } +} + #[cfg(test)] mod tests { use crate::{test, DefaultInMemoryCache}; diff --git a/twilight-cache-inmemory/src/event/guild.rs b/twilight-cache-inmemory/src/event/guild.rs index 0fff7a2bc96..18b93d3241f 100644 --- a/twilight-cache-inmemory/src/event/guild.rs +++ b/twilight-cache-inmemory/src/event/guild.rs @@ -231,6 +231,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: None, user_limit: None, @@ -276,6 +277,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: Some(ThreadMetadata { archived: false, auto_archive_duration: AutoArchiveDuration::Hour, diff --git a/twilight-cache-inmemory/src/event/interaction.rs b/twilight-cache-inmemory/src/event/interaction.rs index 775267c57f7..79726caf596 100644 --- a/twilight-cache-inmemory/src/event/interaction.rs +++ b/twilight-cache-inmemory/src/event/interaction.rs @@ -118,6 +118,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, topic: None, user_limit: None, application_id: None, diff --git a/twilight-cache-inmemory/src/lib.rs b/twilight-cache-inmemory/src/lib.rs index 2647da257bb..04ef7dad0f8 100644 --- a/twilight-cache-inmemory/src/lib.rs +++ b/twilight-cache-inmemory/src/lib.rs @@ -864,6 +864,7 @@ impl Default for InMemoryCache { } mod private { + use twilight_model::gateway::payload::incoming::VoiceChannelStatusUpdate; use twilight_model::gateway::{ event::Event, payload::incoming::{ @@ -922,6 +923,7 @@ mod private { impl Sealed for ThreadUpdate {} impl Sealed for UnavailableGuild {} impl Sealed for UserUpdate {} + impl Sealed for VoiceChannelStatusUpdate {} impl Sealed for VoiceStateUpdate {} impl Sealed for GuildScheduledEventCreate {} impl Sealed for GuildScheduledEventDelete {} @@ -1013,6 +1015,7 @@ impl UpdateCache for Event { Event::ThreadUpdate(v) => cache.update(v.deref()), Event::UnavailableGuild(v) => cache.update(v), Event::UserUpdate(v) => cache.update(v), + Event::VoiceChannelStatusUpdate(v) => cache.update(v), Event::VoiceStateUpdate(v) => cache.update(v.deref()), // Ignored events. diff --git a/twilight-cache-inmemory/src/permission.rs b/twilight-cache-inmemory/src/permission.rs index 483f35ed916..342f995afe6 100644 --- a/twilight-cache-inmemory/src/permission.rs +++ b/twilight-cache-inmemory/src/permission.rs @@ -821,6 +821,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: None, user_limit: None, @@ -866,6 +867,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: None, user_limit: None, diff --git a/twilight-cache-inmemory/src/test.rs b/twilight-cache-inmemory/src/test.rs index 88b43bada12..4653f8af622 100644 --- a/twilight-cache-inmemory/src/test.rs +++ b/twilight-cache-inmemory/src/test.rs @@ -281,6 +281,7 @@ pub fn guild_channel_text() -> (Id, Id, Channel) { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: None, user_limit: None, diff --git a/twilight-cache-inmemory/src/traits.rs b/twilight-cache-inmemory/src/traits.rs index 5a7d9a5c298..f09c2d1fdae 100644 --- a/twilight-cache-inmemory/src/traits.rs +++ b/twilight-cache-inmemory/src/traits.rs @@ -166,6 +166,9 @@ pub trait CacheableChannel: /// Set the last pin timestamp to a new timestamp. fn set_last_pin_timestamp(&mut self, timestamp: Option); + + /// Sets the status of the channel. + fn set_status(&mut self, status: Option); } impl CacheableChannel for Channel { @@ -194,6 +197,10 @@ impl CacheableChannel for Channel { fn set_last_pin_timestamp(&mut self, timestamp: Option) { self.last_pin_timestamp = timestamp; } + + fn set_status(&mut self, status: Option) { + self.status = status; + } } /// Trait for a generic cached representation of a [`Guild`]. diff --git a/twilight-gateway/src/event.rs b/twilight-gateway/src/event.rs index 86e1ce1eb72..bfcc10b35d4 100644 --- a/twilight-gateway/src/event.rs +++ b/twilight-gateway/src/event.rs @@ -189,6 +189,8 @@ bitflags! { const UNAVAILABLE_GUILD = 1 << 40; /// Current user's profile has been updated. const USER_UPDATE = 1 << 41; + /// A voice channel status has been updated. + const VOICE_CHANNEL_STATUS_UPDATE = 1 << 79; /// Voice server has provided an update with voice session details. const VOICE_SERVER_UPDATE = 1 << 42; /// User's state in a voice channel has been updated. @@ -405,6 +407,7 @@ impl From for EventTypeFlags { EventType::TypingStart => Self::TYPING_START, EventType::UnavailableGuild => Self::UNAVAILABLE_GUILD, EventType::UserUpdate => Self::USER_UPDATE, + EventType::VoiceChannelStatusUpdate => Self::VOICE_CHANNEL_STATUS_UPDATE, EventType::VoiceServerUpdate => Self::VOICE_SERVER_UPDATE, EventType::VoiceStateUpdate => Self::VOICE_STATE_UPDATE, EventType::WebhooksUpdate => Self::WEBHOOKS_UPDATE, diff --git a/twilight-http-ratelimiting/src/request.rs b/twilight-http-ratelimiting/src/request.rs index b7f70f0c7c7..a86bc4da9f9 100644 --- a/twilight-http-ratelimiting/src/request.rs +++ b/twilight-http-ratelimiting/src/request.rs @@ -170,6 +170,8 @@ pub enum Path { ChannelsIdThreads(u64), /// Operating on a channel's typing indicator. ChannelsIdTyping(u64), + /// Operating on a channel's voice status. + ChannelsIdVoiceStatus(u64), /// Operating on a channel's webhooks. ChannelsIdWebhooks(u64), /// Operating on an application's entitlements. @@ -397,6 +399,7 @@ impl FromStr for Path { ["channels", id, "thread-members", _] => ChannelsIdThreadMembersId(parse_id(id)?), ["channels", id, "threads"] => ChannelsIdThreads(parse_id(id)?), ["channels", id, "typing"] => ChannelsIdTyping(parse_id(id)?), + ["channels", id, "voice-status"] => ChannelsIdVoiceStatus(parse_id(id)?), ["channels", id, "webhooks"] | ["channels", id, "webhooks", _] => { ChannelsIdWebhooks(parse_id(id)?) } diff --git a/twilight-http/src/client/mod.rs b/twilight-http/src/client/mod.rs index 0379c914381..e3d3939d00f 100644 --- a/twilight-http/src/client/mod.rs +++ b/twilight-http/src/client/mod.rs @@ -4,6 +4,7 @@ mod interaction; pub use self::{builder::ClientBuilder, interaction::InteractionClient}; +use crate::request::channel::SetVoiceChannelStatus; use crate::request::{ application::{ emoji::{ @@ -2882,6 +2883,14 @@ impl Client { DeleteApplicationEmoji::new(self, application_id, emoji_id) } + /// Sets the status of a voice channel. + pub const fn set_voice_channel_status( + &self, + channel_id: Id, + ) -> SetVoiceChannelStatus<'_> { + SetVoiceChannelStatus::new(self, channel_id) + } + /// Execute a request, returning a future resolving to a [`Response`]. /// /// # Errors diff --git a/twilight-http/src/request/channel/mod.rs b/twilight-http/src/request/channel/mod.rs index 7b402806eda..bc2ee7a215d 100644 --- a/twilight-http/src/request/channel/mod.rs +++ b/twilight-http/src/request/channel/mod.rs @@ -14,6 +14,7 @@ mod delete_pin; mod follow_news_channel; mod get_channel; mod get_pins; +mod set_voice_channel_status; mod update_channel; mod update_channel_permission; @@ -22,5 +23,6 @@ pub use self::{ delete_channel::DeleteChannel, delete_channel_permission::DeleteChannelPermission, delete_channel_permission_configured::DeleteChannelPermissionConfigured, delete_pin::DeletePin, follow_news_channel::FollowNewsChannel, get_channel::GetChannel, get_pins::GetPins, - update_channel::UpdateChannel, update_channel_permission::UpdateChannelPermission, + set_voice_channel_status::SetVoiceChannelStatus, update_channel::UpdateChannel, + update_channel_permission::UpdateChannelPermission, }; diff --git a/twilight-http/src/request/channel/set_voice_channel_status.rs b/twilight-http/src/request/channel/set_voice_channel_status.rs new file mode 100644 index 00000000000..1b211159138 --- /dev/null +++ b/twilight-http/src/request/channel/set_voice_channel_status.rs @@ -0,0 +1,72 @@ +use crate::client::Client; +use crate::request::{Request, TryIntoRequest}; +use crate::response::marker::EmptyBody; +use crate::response::ResponseFuture; +use crate::routing::Route; +use crate::{Error, Response}; +use serde::Serialize; +use std::future::IntoFuture; +use twilight_model::id::{marker::ChannelMarker, Id}; +use twilight_validate::channel::{status as validate_status, ChannelValidationError}; + +#[derive(Serialize)] +pub(crate) struct SetVoiceChannelStatusFields<'a> { + #[serde(skip_serializing_if = "Option::is_none")] + status: Option<&'a str>, +} + +/// Sets the status of a voice channel. +#[must_use = "requests must be configured and executed"] +pub struct SetVoiceChannelStatus<'a> { + channel_id: Id, + fields: Result, ChannelValidationError>, + http: &'a Client, +} + +impl<'a> SetVoiceChannelStatus<'a> { + pub(crate) const fn new(http: &'a Client, channel_id: Id) -> Self { + Self { + channel_id, + fields: Ok(SetVoiceChannelStatusFields { status: None }), + http, + } + } + + pub fn status(mut self, status: &'a str) -> Self { + self.fields = self.fields.and_then(|mut fields| { + validate_status(status)?; + fields.status.replace(status); + + Ok(fields) + }); + + self + } +} + +impl IntoFuture for SetVoiceChannelStatus<'_> { + type Output = Result, Error>; + + type IntoFuture = ResponseFuture; + + fn into_future(self) -> Self::IntoFuture { + let http = self.http; + + match self.try_into_request() { + Ok(request) => http.request(request), + Err(source) => ResponseFuture::error(source), + } + } +} + +impl TryIntoRequest for SetVoiceChannelStatus<'_> { + fn try_into_request(self) -> Result { + let fields = self.fields.map_err(Error::validation)?; + + Request::builder(&Route::SetVoiceChannelStatus { + channel_id: self.channel_id.get(), + }) + .json(&fields) + .build() + } +} diff --git a/twilight-http/src/request/try_into_request.rs b/twilight-http/src/request/try_into_request.rs index a86b7a6cce4..a6546f0226f 100644 --- a/twilight-http/src/request/try_into_request.rs +++ b/twilight-http/src/request/try_into_request.rs @@ -1,4 +1,5 @@ mod private { + use crate::request::channel::SetVoiceChannelStatus; use crate::request::{ application::{ command::{ @@ -265,6 +266,7 @@ mod private { impl Sealed for SearchGuildMembers<'_> {} impl Sealed for SetGlobalCommands<'_> {} impl Sealed for SetGuildCommands<'_> {} + impl Sealed for SetVoiceChannelStatus<'_> {} impl Sealed for SyncTemplate<'_> {} impl Sealed for UpdateAutoModerationRule<'_> {} impl Sealed for UpdateChannel<'_> {} diff --git a/twilight-http/src/routing.rs b/twilight-http/src/routing.rs index cffa735f22b..6db33396dd1 100644 --- a/twilight-http/src/routing.rs +++ b/twilight-http/src/routing.rs @@ -996,6 +996,11 @@ pub enum Route<'a> { /// The ID of the guild. guild_id: u64, }, + /// Route information to set voice channel status. + SetVoiceChannelStatus { + /// The ID of the channel. + channel_id: u64, + }, /// Route information to sync a guild's integration. SyncGuildIntegration { /// The ID of the guild. @@ -1406,6 +1411,7 @@ impl Route<'_> { | Self::PinMessage { .. } | Self::SetGlobalCommands { .. } | Self::SetGuildCommands { .. } + | Self::SetVoiceChannelStatus { .. } | Self::SyncTemplate { .. } | Self::UpdateCommandPermissions { .. } | Self::UpdateGuildOnboarding { .. } @@ -1742,6 +1748,7 @@ impl Route<'_> { Self::EndPoll { channel_id, .. } | Self::GetAnswerVoters { channel_id, .. } => { Path::ChannelsIdPolls(channel_id) } + Self::SetVoiceChannelStatus { channel_id } => Path::ChannelsIdVoiceStatus(channel_id), } } } @@ -2066,6 +2073,12 @@ impl Display for Route<'_> { f.write_str("/typing") } + Route::SetVoiceChannelStatus { channel_id } => { + f.write_str("channels/")?; + Display::fmt(channel_id, f)?; + + f.write_str("/voice-status") + } Route::CreateWebhook { channel_id } | Route::GetChannelWebhooks { channel_id } => { f.write_str("channels/")?; Display::fmt(channel_id, f)?; @@ -4864,4 +4877,15 @@ mod tests { let route = Route::GetSKUs { application_id: 1 }; assert_eq!(route.to_string(), format!("applications/1/skus")); } + + #[test] + fn set_voice_channel_status() { + let route = Route::SetVoiceChannelStatus { + channel_id: CHANNEL_ID, + }; + assert_eq!( + route.to_string(), + format!("channels/{CHANNEL_ID}/voice-status") + ); + } } diff --git a/twilight-model/src/application/interaction/mod.rs b/twilight-model/src/application/interaction/mod.rs index 42190c11744..ddefa6f631c 100644 --- a/twilight-model/src/application/interaction/mod.rs +++ b/twilight-model/src/application/interaction/mod.rs @@ -562,6 +562,7 @@ mod tests { member_count: None, message_count: None, newly_created: None, + status: None, thread_metadata: None, video_quality_mode: None, }), diff --git a/twilight-model/src/channel/mod.rs b/twilight-model/src/channel/mod.rs index 301fcafa1a4..b8614db5806 100644 --- a/twilight-model/src/channel/mod.rs +++ b/twilight-model/src/channel/mod.rs @@ -170,6 +170,8 @@ pub struct Channel { /// Defaults to automatic for applicable channels. #[serde(skip_serializing_if = "Option::is_none")] pub rtc_region: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, /// Metadata about a thread. #[serde(skip_serializing_if = "Option::is_none")] pub thread_metadata: Option, @@ -261,6 +263,7 @@ mod tests { rate_limit_per_user: Some(0), recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: Some("a".to_owned()), user_limit: None, @@ -304,6 +307,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: None, user_limit: None, @@ -359,6 +363,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: Some("a news channel".to_owned()), user_limit: None, @@ -428,6 +433,7 @@ mod tests { rate_limit_per_user: Some(1000), recipients: None, rtc_region: None, + status: None, thread_metadata: Some(ThreadMetadata { archived: false, auto_archive_duration: AutoArchiveDuration::Day, @@ -518,6 +524,7 @@ mod tests { rate_limit_per_user: Some(1000), recipients: None, rtc_region: None, + status: None, thread_metadata: Some(ThreadMetadata { archived: false, auto_archive_duration: AutoArchiveDuration::Day, @@ -616,6 +623,7 @@ mod tests { rate_limit_per_user: Some(1000), recipients: None, rtc_region: None, + status: None, thread_metadata: Some(ThreadMetadata { archived: false, auto_archive_duration: AutoArchiveDuration::Day, diff --git a/twilight-model/src/gateway/event/dispatch.rs b/twilight-model/src/gateway/event/dispatch.rs index 6ced713cca6..20b98147d9b 100644 --- a/twilight-model/src/gateway/event/dispatch.rs +++ b/twilight-model/src/gateway/event/dispatch.rs @@ -78,6 +78,7 @@ pub enum DispatchEvent { TypingStart(Box), UnavailableGuild(UnavailableGuild), UserUpdate(UserUpdate), + VoiceChannelStatusUpdate(VoiceChannelStatusUpdate), VoiceServerUpdate(VoiceServerUpdate), VoiceStateUpdate(Box), WebhooksUpdate(WebhooksUpdate), @@ -151,6 +152,7 @@ impl DispatchEvent { Self::TypingStart(_) => EventType::TypingStart, Self::UnavailableGuild(_) => EventType::UnavailableGuild, Self::UserUpdate(_) => EventType::UserUpdate, + Self::VoiceChannelStatusUpdate(_) => EventType::VoiceChannelStatusUpdate, Self::VoiceServerUpdate(_) => EventType::VoiceServerUpdate, Self::VoiceStateUpdate(_) => EventType::VoiceStateUpdate, Self::WebhooksUpdate(_) => EventType::WebhooksUpdate, @@ -426,6 +428,9 @@ impl<'de> DeserializeSeed<'de> for DispatchEventWithTypeDeserializer<'_> { DispatchEvent::TypingStart(Box::new(TypingStart::deserialize(deserializer)?)) } "USER_UPDATE" => DispatchEvent::UserUpdate(UserUpdate::deserialize(deserializer)?), + "VOICE_CHANNEL_STATUS_UPDATE" => DispatchEvent::VoiceChannelStatusUpdate( + VoiceChannelStatusUpdate::deserialize(deserializer)?, + ), "VOICE_SERVER_UPDATE" => { DispatchEvent::VoiceServerUpdate(VoiceServerUpdate::deserialize(deserializer)?) } diff --git a/twilight-model/src/gateway/event/kind.rs b/twilight-model/src/gateway/event/kind.rs index 5dfeeb77f10..748cbc7dd02 100644 --- a/twilight-model/src/gateway/event/kind.rs +++ b/twilight-model/src/gateway/event/kind.rs @@ -88,6 +88,7 @@ pub enum EventType { TypingStart, UnavailableGuild, UserUpdate, + VoiceChannelStatusUpdate, VoiceServerUpdate, VoiceStateUpdate, WebhooksUpdate, @@ -160,6 +161,7 @@ impl EventType { Self::TypingStart => Some("TYPING_START"), Self::UnavailableGuild => Some("UNAVAILABLE_GUILD"), Self::UserUpdate => Some("USER_UPDATE"), + Self::VoiceChannelStatusUpdate => Some("VOICE_CHANNEL_STATUS_UPDATE"), Self::VoiceServerUpdate => Some("VOICE_SERVER_UPDATE"), Self::VoiceStateUpdate => Some("VOICE_STATE_UPDATE"), Self::WebhooksUpdate => Some("WEBHOOKS_UPDATE"), @@ -239,6 +241,7 @@ impl<'a> TryFrom<&'a str> for EventType { "TYPING_START" => Ok(Self::TypingStart), "UNAVAILABLE_GUILD" => Ok(Self::UnavailableGuild), "USER_UPDATE" => Ok(Self::UserUpdate), + "VOICE_CHANNEL_STATUS_UPDATE" => Ok(Self::VoiceChannelStatusUpdate), "VOICE_SERVER_UPDATE" => Ok(Self::VoiceServerUpdate), "VOICE_STATE_UPDATE" => Ok(Self::VoiceStateUpdate), "WEBHOOKS_UPDATE" => Ok(Self::WebhooksUpdate), @@ -373,6 +376,10 @@ mod tests { assert_variant(EventType::TypingStart, "TYPING_START"); assert_variant(EventType::UnavailableGuild, "UNAVAILABLE_GUILD"); assert_variant(EventType::UserUpdate, "USER_UPDATE"); + assert_variant( + EventType::VoiceChannelStatusUpdate, + "VOICE_CHANNEL_STATUS_UPDATE", + ); assert_variant(EventType::VoiceServerUpdate, "VOICE_SERVER_UPDATE"); assert_variant(EventType::VoiceStateUpdate, "VOICE_STATE_UPDATE"); assert_variant(EventType::WebhooksUpdate, "WEBHOOKS_UPDATE"); diff --git a/twilight-model/src/gateway/event/mod.rs b/twilight-model/src/gateway/event/mod.rs index 0d98a597c76..e727b475174 100644 --- a/twilight-model/src/gateway/event/mod.rs +++ b/twilight-model/src/gateway/event/mod.rs @@ -173,6 +173,8 @@ pub enum Event { UnavailableGuild(UnavailableGuild), /// The current user was updated. UserUpdate(UserUpdate), + /// A voice channel status was updated. + VoiceChannelStatusUpdate(VoiceChannelStatusUpdate), /// A voice server update was sent. VoiceServerUpdate(VoiceServerUpdate), /// A voice state in a voice channel was updated. @@ -243,6 +245,7 @@ impl Event { Event::ThreadUpdate(e) => e.0.guild_id, Event::TypingStart(e) => e.guild_id, Event::UnavailableGuild(e) => Some(e.id), + Event::VoiceChannelStatusUpdate(e) => Some(e.guild_id), Event::VoiceServerUpdate(e) => Some(e.guild_id), Event::VoiceStateUpdate(e) => e.0.guild_id, Event::WebhooksUpdate(e) => Some(e.guild_id), @@ -333,6 +336,7 @@ impl Event { Self::TypingStart(_) => EventType::TypingStart, Self::UnavailableGuild(_) => EventType::UnavailableGuild, Self::UserUpdate(_) => EventType::UserUpdate, + Self::VoiceChannelStatusUpdate(_) => EventType::VoiceChannelStatusUpdate, Self::VoiceServerUpdate(_) => EventType::VoiceServerUpdate, Self::VoiceStateUpdate(_) => EventType::VoiceStateUpdate, Self::WebhooksUpdate(_) => EventType::WebhooksUpdate, @@ -411,6 +415,7 @@ impl From for Event { DispatchEvent::TypingStart(v) => Self::TypingStart(v), DispatchEvent::UnavailableGuild(v) => Self::UnavailableGuild(v), DispatchEvent::UserUpdate(v) => Self::UserUpdate(v), + DispatchEvent::VoiceChannelStatusUpdate(v) => Self::VoiceChannelStatusUpdate(v), DispatchEvent::VoiceServerUpdate(v) => Self::VoiceServerUpdate(v), DispatchEvent::VoiceStateUpdate(v) => Self::VoiceStateUpdate(v), DispatchEvent::WebhooksUpdate(v) => Self::WebhooksUpdate(v), @@ -548,6 +553,7 @@ mod tests { const_assert!(mem::size_of::() <= EVENT_THRESHOLD); const_assert!(mem::size_of::() <= EVENT_THRESHOLD); const_assert!(mem::size_of::() <= EVENT_THRESHOLD); + const_assert!(mem::size_of::() <= EVENT_THRESHOLD); const_assert!(mem::size_of::() <= EVENT_THRESHOLD); const_assert!(mem::size_of::() <= EVENT_THRESHOLD); const_assert!(mem::size_of::() <= EVENT_THRESHOLD); diff --git a/twilight-model/src/gateway/payload/incoming/mod.rs b/twilight-model/src/gateway/payload/incoming/mod.rs index d2dfac3274a..1c25aaf315d 100644 --- a/twilight-model/src/gateway/payload/incoming/mod.rs +++ b/twilight-model/src/gateway/payload/incoming/mod.rs @@ -76,6 +76,7 @@ mod thread_update; mod typing_start; mod unavailable_guild; mod user_update; +mod voice_channel_status_update; mod voice_server_update; mod voice_state_update; mod webhooks_update; @@ -113,6 +114,7 @@ pub use self::{ thread_delete::ThreadDelete, thread_list_sync::ThreadListSync, thread_member_update::ThreadMemberUpdate, thread_members_update::ThreadMembersUpdate, thread_update::ThreadUpdate, typing_start::TypingStart, unavailable_guild::UnavailableGuild, - user_update::UserUpdate, voice_server_update::VoiceServerUpdate, - voice_state_update::VoiceStateUpdate, webhooks_update::WebhooksUpdate, + user_update::UserUpdate, voice_channel_status_update::VoiceChannelStatusUpdate, + voice_server_update::VoiceServerUpdate, voice_state_update::VoiceStateUpdate, + webhooks_update::WebhooksUpdate, }; diff --git a/twilight-model/src/gateway/payload/incoming/voice_channel_status_update.rs b/twilight-model/src/gateway/payload/incoming/voice_channel_status_update.rs new file mode 100644 index 00000000000..7be24ead41a --- /dev/null +++ b/twilight-model/src/gateway/payload/incoming/voice_channel_status_update.rs @@ -0,0 +1,12 @@ +use crate::id::{ + marker::{ChannelMarker, GuildMarker}, + Id, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct VoiceChannelStatusUpdate { + pub guild_id: Id, + pub id: Id, + pub status: Option, +} diff --git a/twilight-model/src/guild/audit_log/event_type.rs b/twilight-model/src/guild/audit_log/event_type.rs index c7dc13ada7d..a867a8aa610 100644 --- a/twilight-model/src/guild/audit_log/event_type.rs +++ b/twilight-model/src/guild/audit_log/event_type.rs @@ -230,6 +230,10 @@ pub enum AuditLogEventType { CreatorMonetizationRequestCreated, /// Creator monetization terms were accepted. CreatorMonetizationTermsAccepted, + /// A voice channel status was updated by a user. + VoiceChannelStatusUpdate, + /// A voice channel status was deleted by a user. + VoiceChannelStatusDelete, /// Variant value is unknown to the library. Unknown(u16), } @@ -293,6 +297,8 @@ impl From for AuditLogEventType { 145 => AuditLogEventType::AutoModerationUserCommunicationDisabled, 150 => AuditLogEventType::CreatorMonetizationRequestCreated, 151 => AuditLogEventType::CreatorMonetizationTermsAccepted, + 192 => AuditLogEventType::VoiceChannelStatusUpdate, + 193 => AuditLogEventType::VoiceChannelStatusDelete, unknown => AuditLogEventType::Unknown(unknown), } } @@ -357,6 +363,8 @@ impl From for u16 { AuditLogEventType::AutoModerationUserCommunicationDisabled => 145, AuditLogEventType::CreatorMonetizationRequestCreated => 150, AuditLogEventType::CreatorMonetizationTermsAccepted => 151, + AuditLogEventType::VoiceChannelStatusUpdate => 192, + AuditLogEventType::VoiceChannelStatusDelete => 193, AuditLogEventType::Unknown(unknown) => unknown, } } @@ -457,6 +465,8 @@ mod tests { 151, u16::from(AuditLogEventType::CreatorMonetizationTermsAccepted) ); + assert_eq!(192, u16::from(AuditLogEventType::VoiceChannelStatusUpdate)); + assert_eq!(193, u16::from(AuditLogEventType::VoiceChannelStatusDelete)); assert_eq!(250, u16::from(AuditLogEventType::Unknown(250))); } } diff --git a/twilight-model/src/guild/audit_log/optional_entry_info.rs b/twilight-model/src/guild/audit_log/optional_entry_info.rs index b9161618f41..c5ba7136718 100644 --- a/twilight-model/src/guild/audit_log/optional_entry_info.rs +++ b/twilight-model/src/guild/audit_log/optional_entry_info.rs @@ -159,6 +159,17 @@ pub struct AuditLogOptionalEntryInfo { /// [`AuditLogEventType::ChannelOverwriteUpdate`]: super::AuditLogEventType::ChannelOverwriteUpdate #[serde(skip_serializing_if = "Option::is_none")] pub role_name: Option, + /// Status of a voice channel. + /// + /// The following events have this option: + /// + /// - [`AuditLogEventType::VoiceChannelStatusUpdate`] + /// - [`AuditLogEventType::VoiceChannelStatusDelete`] + /// + /// [`AuditLogEventType::VoiceChannelStatusUpdate`]: super::AuditLogEventType::VoiceChannelStatusUpdate + /// [`AuditLogEventType::VoiceChannelStatusDelete`]: super::AuditLogEventType::VoiceChannelStatusDelete + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, } #[cfg(test)] @@ -176,7 +187,8 @@ mod tests { kind, members_removed, message_id, - role_name + role_name, + status ); assert_impl_all!( AuditLogOptionalEntryInfo: Clone, diff --git a/twilight-model/src/guild/permissions.rs b/twilight-model/src/guild/permissions.rs index afc67dcf752..b3ec176dc4b 100644 --- a/twilight-model/src/guild/permissions.rs +++ b/twilight-model/src/guild/permissions.rs @@ -77,6 +77,8 @@ bitflags! { const USE_EXTERNAL_SOUNDS = 1 << 45; /// Allows sending voice messages const SEND_VOICE_MESSAGES = 1 << 46; + /// Allows setting voice channel statuses + const SET_VOICE_CHANNEL_STATUS = 1 << 48; /// Allows sending polls. const SEND_POLLS = 1 << 49; /// Allows user-installed apps to send public responses. When diff --git a/twilight-model/src/guild/template/mod.rs b/twilight-model/src/guild/template/mod.rs index 0fe042a4f6c..2648408c54a 100644 --- a/twilight-model/src/guild/template/mod.rs +++ b/twilight-model/src/guild/template/mod.rs @@ -239,6 +239,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: None, user_limit: None, @@ -289,6 +290,7 @@ mod tests { rate_limit_per_user: Some(0), recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: None, user_limit: None, @@ -326,6 +328,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: None, user_limit: None, @@ -363,6 +366,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, thread_metadata: None, topic: None, user_limit: Some(0), diff --git a/twilight-standby/src/lib.rs b/twilight-standby/src/lib.rs index 1f4fe4d53b6..1cb25dc474b 100644 --- a/twilight-standby/src/lib.rs +++ b/twilight-standby/src/lib.rs @@ -1182,6 +1182,7 @@ mod tests { rate_limit_per_user: None, recipients: None, rtc_region: None, + status: None, topic: None, user_limit: None, application_id: None, diff --git a/twilight-validate/src/channel.rs b/twilight-validate/src/channel.rs index 8de2bd5f473..caf007ed704 100644 --- a/twilight-validate/src/channel.rs +++ b/twilight-validate/src/channel.rs @@ -27,6 +27,9 @@ pub const CHANNEL_NAME_LENGTH_MIN: usize = 1; /// Maximum length of a channel's rate limit per user. pub const CHANNEL_RATE_LIMIT_PER_USER_MAX: u16 = 21_600; +/// Maximum length of a (voice) channel's status. +pub const CHANNEL_STATUS_LENGTH_MAX: usize = 500; + /// Maximum number of members that can be returned in a thread. pub const CHANNEL_THREAD_GET_MEMBERS_LIMIT_MAX: u32 = 100; @@ -95,6 +98,9 @@ impl Display for ChannelValidationError { ChannelValidationErrorType::RateLimitPerUserInvalid { .. } => { f.write_str("the rate limit per user is invalid") } + ChannelValidationErrorType::StatusInvalid => { + f.write_str("the length of status is invalid") + } ChannelValidationErrorType::ThreadMemberLimitInvalid => { f.write_str("number of members to return is less than ")?; Display::fmt(&CHANNEL_THREAD_GET_MEMBERS_LIMIT_MIN, f)?; @@ -137,6 +143,8 @@ pub enum ChannelValidationErrorType { /// Provided ratelimit is invalid. rate_limit_per_user: u16, }, + /// The length of the status is more than 500 UTF-16 characters. + StatusInvalid, /// The number of members to return is less than 1 or greater than 100. ThreadMemberLimitInvalid, /// The length of the topic is more than 1024 UTF-16 characters. @@ -276,6 +284,23 @@ pub const fn rate_limit_per_user(value: u16) -> Result<(), ChannelValidationErro } } +/// Ensures a channel's status length is correct. +/// +/// The length of the status must be at most [`CHANNEL_STATUS_LENGTH_MAX`]. +/// +/// # Errors +/// +/// Returns an error of type [`StatusInvalid`] if the status is of invalid length. +pub fn status(value: impl AsRef) -> Result<(), ChannelValidationError> { + if value.as_ref().chars().count() <= CHANNEL_STATUS_LENGTH_MAX { + Ok(()) + } else { + Err(ChannelValidationError { + kind: ChannelValidationErrorType::StatusInvalid, + }) + } +} + /// Ensure the limit set for the number of thread members to return is correct. /// /// The value must be at least [`CHANNEL_THREAD_GET_MEMBERS_LIMIT_MIN`] and at most