diff --git a/twilight-cache-inmemory/src/event/guild.rs b/twilight-cache-inmemory/src/event/guild.rs index 7a580eda6c3..a7e217f972d 100644 --- a/twilight-cache-inmemory/src/event/guild.rs +++ b/twilight-cache-inmemory/src/event/guild.rs @@ -51,6 +51,7 @@ impl InMemoryCache { public_updates_channel_id, roles, rules_channel_id, + safety_alerts_channel_id, splash, stage_instances, stickers, @@ -151,6 +152,7 @@ impl InMemoryCache { premium_tier, public_updates_channel_id, rules_channel_id, + safety_alerts_channel_id, splash, system_channel_id, system_channel_flags, @@ -432,6 +434,7 @@ mod tests { public_updates_channel_id: None, roles: Vec::new(), rules_channel_id: None, + safety_alerts_channel_id: Some(Id::new(789)), splash: None, stage_instances: Vec::new(), stickers: Vec::new(), diff --git a/twilight-cache-inmemory/src/model/guild.rs b/twilight-cache-inmemory/src/model/guild.rs index 5416f787876..b8c5dfee168 100644 --- a/twilight-cache-inmemory/src/model/guild.rs +++ b/twilight-cache-inmemory/src/model/guild.rs @@ -47,6 +47,7 @@ pub struct CachedGuild { pub(crate) premium_tier: PremiumTier, pub(crate) public_updates_channel_id: Option>, pub(crate) rules_channel_id: Option>, + pub(crate) safety_alerts_channel_id: Option>, pub(crate) splash: Option, pub(crate) system_channel_id: Option>, pub(crate) system_channel_flags: SystemChannelFlags, @@ -222,6 +223,11 @@ impl CachedGuild { self.rules_channel_id } + /// The ID of the channel where admins and moderators of Community guilds receive safety alerts from Discord. + pub const fn safety_alerts_channel_id(&self) -> Option> { + self.safety_alerts_channel_id + } + /// Splash hash. /// /// See [Discord Docs/Image Formatting]. diff --git a/twilight-cache-inmemory/src/permission.rs b/twilight-cache-inmemory/src/permission.rs index 3ef0f218907..d6fc3a9e2c1 100644 --- a/twilight-cache-inmemory/src/permission.rs +++ b/twilight-cache-inmemory/src/permission.rs @@ -703,6 +703,9 @@ mod tests { /// ID of a thread created in the general channel. const THREAD_ID: Id = Id::new(5); + /// ID of the safety alerts channel. + const SAFETY_ALERTS_CHANNEL_ID: Id = Id::new(6); + fn base_guild() -> Guild { Guild { id: GUILD_ID, @@ -744,6 +747,7 @@ mod tests { Permissions::CREATE_INVITE | Permissions::VIEW_AUDIT_LOG, ), ]), + safety_alerts_channel_id: Some(SAFETY_ALERTS_CHANNEL_ID), splash: None, stage_instances: Vec::new(), stickers: Vec::new(), diff --git a/twilight-cache-inmemory/src/test.rs b/twilight-cache-inmemory/src/test.rs index 1b1935c03aa..dc2abb83fe6 100644 --- a/twilight-cache-inmemory/src/test.rs +++ b/twilight-cache-inmemory/src/test.rs @@ -403,6 +403,7 @@ pub fn guild(id: Id, member_count: Option) -> Guild { public_updates_channel_id: None, roles: Vec::new(), rules_channel_id: None, + safety_alerts_channel_id: Some(Id::new(2)), splash: None, stage_instances: Vec::new(), stickers: Vec::new(), diff --git a/twilight-model/src/guild/auto_moderation/mod.rs b/twilight-model/src/guild/auto_moderation/mod.rs index 33370ac1780..95d0bf784ec 100644 --- a/twilight-model/src/guild/auto_moderation/mod.rs +++ b/twilight-model/src/guild/auto_moderation/mod.rs @@ -147,8 +147,9 @@ mod tests { allow_list: None, keyword_filter: Some(Vec::from(["shoot".into(), "darn".into()])), presets: None, - regex_patterns: Some(Vec::from(["[a-z]+".into(), "[0-9]+".into()])), + mention_raid_protection_enabled: Some(true), mention_total_limit: None, + regex_patterns: Some(Vec::from(["[a-z]+".into(), "[0-9]+".into()])), }, trigger_type: AutoModerationTriggerType::Keyword, }; @@ -233,7 +234,7 @@ mod tests { Token::Str("trigger_metadata"), Token::Struct { name: "AutoModerationTriggerMetadata", - len: 2, + len: 3, }, Token::Str("keyword_filter"), Token::Some, @@ -241,6 +242,9 @@ mod tests { Token::Str("shoot"), Token::Str("darn"), Token::SeqEnd, + Token::Str("mention_raid_protection_enabled"), + Token::Some, + Token::Bool(true), Token::Str("regex_patterns"), Token::Some, Token::Seq { len: Some(2) }, diff --git a/twilight-model/src/guild/auto_moderation/trigger_metadata.rs b/twilight-model/src/guild/auto_moderation/trigger_metadata.rs index 44240a5c6dd..ab7a00b6f15 100644 --- a/twilight-model/src/guild/auto_moderation/trigger_metadata.rs +++ b/twilight-model/src/guild/auto_moderation/trigger_metadata.rs @@ -23,6 +23,9 @@ pub struct AutoModerationTriggerMetadata { /// [Discord Docs/Keyword Matching Strategies]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-matching-strategies #[serde(skip_serializing_if = "Option::is_none")] pub presets: Option>, + /// Whether to automatically detect mention raids. + #[serde(skip_serializing_if = "Option::is_none")] + pub mention_raid_protection_enabled: Option, /// Total number of unique role and user mentions allowed per message (Maximum of 50). #[serde(skip_serializing_if = "Option::is_none")] pub mention_total_limit: Option, @@ -63,6 +66,7 @@ mod tests { AutoModerationKeywordPresetType::SexualContent, AutoModerationKeywordPresetType::Slurs, ])), + mention_raid_protection_enabled: Some(true), mention_total_limit: Some(5), regex_patterns: Some(Vec::from(["^\\d+$".into()])), }; @@ -72,7 +76,7 @@ mod tests { &[ Token::Struct { name: "AutoModerationTriggerMetadata", - len: 5, + len: 6, }, Token::Str("allow_list"), Token::Some, @@ -92,6 +96,9 @@ mod tests { Token::U8(u8::from(AutoModerationKeywordPresetType::SexualContent)), Token::U8(u8::from(AutoModerationKeywordPresetType::Slurs)), Token::SeqEnd, + Token::Str("mention_raid_protection_enabled"), + Token::Some, + Token::Bool(true), Token::Str("mention_total_limit"), Token::Some, Token::U8(5), diff --git a/twilight-model/src/guild/feature.rs b/twilight-model/src/guild/feature.rs index 18f8b74c99d..da3bea02a83 100644 --- a/twilight-model/src/guild/feature.rs +++ b/twilight-model/src/guild/feature.rs @@ -55,6 +55,8 @@ pub enum GuildFeature { PreviewEnabled, /// Has access to create private threads. PrivateThreads, + /// Guild has disabled alerts for join raids in the configured safety alerts channel. + RaidAlertsDisabled, /// Is able to set role icons. RoleIcons, /// Guild has role subscriptions that can be purchased. @@ -100,6 +102,7 @@ impl From for Cow<'static, str> { GuildFeature::Partnered => "PARTNERED".into(), GuildFeature::PreviewEnabled => "PREVIEW_ENABLED".into(), GuildFeature::PrivateThreads => "PRIVATE_THREADS".into(), + GuildFeature::RaidAlertsDisabled => "RAID_ALERTS_DISABLED".into(), GuildFeature::RoleIcons => "ROLE_ICONS".into(), GuildFeature::RoleSubscriptionsAvailableForPurchase => { "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE".into() @@ -138,6 +141,7 @@ impl From for GuildFeature { "PARTNERED" => Self::Partnered, "PREVIEW_ENABLED" => Self::PreviewEnabled, "PRIVATE_THREADS" => Self::PrivateThreads, + "RAID_ALERTS_DISABLED" => Self::RaidAlertsDisabled, "ROLE_ICONS" => Self::RoleIcons, "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE" => { GuildFeature::RoleSubscriptionsAvailableForPurchase diff --git a/twilight-model/src/guild/mod.rs b/twilight-model/src/guild/mod.rs index 65f0aa570f7..742d544eb55 100644 --- a/twilight-model/src/guild/mod.rs +++ b/twilight-model/src/guild/mod.rs @@ -130,6 +130,9 @@ pub struct Guild { pub public_updates_channel_id: Option>, pub roles: Vec, pub rules_channel_id: Option>, + /// The ID of the channel where admins and moderators of Community guilds receive safety alerts from Discord. + #[serde(skip_serializing_if = "Option::is_none")] + pub safety_alerts_channel_id: Option>, pub splash: Option, #[serde(skip_serializing_if = "Vec::is_empty")] pub stage_instances: Vec, @@ -192,6 +195,7 @@ impl<'de> Deserialize<'de> for Guild { Presences, PublicUpdatesChannelId, Roles, + SafetyAlertsChannelId, Splash, StageInstances, Stickers, @@ -253,6 +257,7 @@ impl<'de> Deserialize<'de> for Guild { let mut presences = None; let mut public_updates_channel_id = None::>; let mut roles = None; + let mut safety_alerts_channel_id = None::>; let mut splash = None::>; let mut stage_instances = None::>; let mut stickers = None::>; @@ -543,6 +548,13 @@ impl<'de> Deserialize<'de> for Guild { roles = Some(map.next_value()?); } + Field::SafetyAlertsChannelId => { + if safety_alerts_channel_id.is_some() { + return Err(DeError::duplicate_field("safety_alerts_channel_id")); + } + + safety_alerts_channel_id = Some(map.next_value()?); + } Field::Splash => { if splash.is_some() { return Err(DeError::duplicate_field("splash")); @@ -683,6 +695,7 @@ impl<'de> Deserialize<'de> for Guild { let mut presences = presences.unwrap_or_default(); let public_updates_channel_id = public_updates_channel_id.unwrap_or_default(); let rules_channel_id = rules_channel_id.unwrap_or_default(); + let safety_alerts_channel_id = safety_alerts_channel_id.unwrap_or_default(); let splash = splash.unwrap_or_default(); let stage_instances = stage_instances.unwrap_or_default(); let stickers = stickers.unwrap_or_default(); @@ -734,6 +747,7 @@ impl<'de> Deserialize<'de> for Guild { ?public_updates_channel_id, ?rules_channel_id, ?roles, + ?safety_alerts_channel_id, ?splash, ?stage_instances, ?stickers, @@ -801,6 +815,7 @@ impl<'de> Deserialize<'de> for Guild { public_updates_channel_id, roles, rules_channel_id, + safety_alerts_channel_id, splash, stage_instances, stickers, @@ -926,6 +941,7 @@ mod tests { public_updates_channel_id: None, roles: Vec::new(), rules_channel_id: Some(Id::new(6)), + safety_alerts_channel_id: Some(Id::new(9)), splash: Some(image_hash::SPLASH), stage_instances: Vec::new(), stickers: Vec::new(), @@ -945,7 +961,7 @@ mod tests { &[ Token::Struct { name: "Guild", - len: 46, + len: 47, }, Token::Str("afk_channel_id"), Token::Some, @@ -1049,6 +1065,10 @@ mod tests { Token::Some, Token::NewtypeStruct { name: "Id" }, Token::Str("6"), + Token::Str("safety_alerts_channel_id"), + Token::Some, + Token::NewtypeStruct { name: "Id" }, + Token::Str("9"), Token::Str("splash"), Token::Some, Token::Str(image_hash::SPLASH_INPUT),