twilight_http/request/guild/auto_moderation/create_auto_moderation_rule.rs
1use crate::{
2 client::Client,
3 error::Error as HttpError,
4 request::{self, AuditLogReason, Request, TryIntoRequest},
5 response::ResponseFuture,
6 routing::Route,
7};
8use serde::Serialize;
9use twilight_model::{
10 guild::auto_moderation::{
11 AutoModerationActionType, AutoModerationEventType, AutoModerationKeywordPresetType,
12 AutoModerationRule, AutoModerationTriggerType,
13 },
14 id::{
15 Id,
16 marker::{ChannelMarker, GuildMarker, RoleMarker},
17 },
18};
19use twilight_validate::request::{
20 ValidationError, audit_reason as validate_audit_reason,
21 auto_moderation_action_metadata_duration_seconds as validate_auto_moderation_action_metadata_duration_seconds,
22 auto_moderation_block_action_custom_message_limit as validate_auto_moderation_block_action_custom_message_limit,
23 auto_moderation_exempt_channels as validate_auto_moderation_exempt_channels,
24 auto_moderation_exempt_roles as validate_auto_moderation_exempt_roles,
25 auto_moderation_metadata_keyword_allow_list as validate_auto_moderation_metadata_keyword_allow_list,
26 auto_moderation_metadata_keyword_filter as validate_auto_moderation_metadata_keyword_filter,
27 auto_moderation_metadata_mention_total_limit as validate_auto_moderation_metadata_mention_total_limit,
28 auto_moderation_metadata_regex_patterns as validate_auto_moderation_metadata_regex_patterns,
29};
30
31#[derive(Serialize)]
32struct CreateAutoModerationRuleFieldsAction {
33 /// Type of action.
34 #[serde(rename = "type")]
35 pub kind: AutoModerationActionType,
36 /// Additional metadata needed during execution for this specific action
37 /// type.
38 pub metadata: CreateAutoModerationRuleFieldsActionMetadata,
39}
40
41#[derive(Default, Serialize)]
42struct CreateAutoModerationRuleFieldsActionMetadata {
43 /// Channel to which user content should be logged.
44 pub channel_id: Option<Id<ChannelMarker>>,
45 /// Additional explanation that will be shown to members whenever their message is blocked.
46 ///
47 /// Maximum value length is 150 characters.
48 pub custom_message: Option<String>,
49 /// Timeout duration in seconds.
50 ///
51 /// Maximum value is 2419200 seconds, or 4 weeks.
52 pub duration_seconds: Option<u32>,
53}
54
55#[derive(Serialize)]
56struct CreateAutoModerationRuleFieldsTriggerMetadata<'a> {
57 #[serde(skip_serializing_if = "Option::is_none")]
58 allow_list: Option<&'a [&'a str]>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 keyword_filter: Option<&'a [&'a str]>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 presets: Option<&'a [AutoModerationKeywordPresetType]>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 mention_total_limit: Option<u8>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 regex_patterns: Option<&'a [&'a str]>,
67}
68
69#[derive(Serialize)]
70struct CreateAutoModerationRuleFields<'a> {
71 actions: Option<Vec<CreateAutoModerationRuleFieldsAction>>,
72 enabled: Option<bool>,
73 event_type: AutoModerationEventType,
74 exempt_channels: Option<&'a [Id<ChannelMarker>]>,
75 exempt_roles: Option<&'a [Id<RoleMarker>]>,
76 name: &'a str,
77 trigger_metadata: Option<CreateAutoModerationRuleFieldsTriggerMetadata<'a>>,
78 trigger_type: Option<AutoModerationTriggerType>,
79}
80
81/// Create an auto moderation rule within a guild.
82///
83/// Requires the [`MANAGE_GUILD`] permission.
84///
85/// # Examples
86///
87/// Create a rule that deletes messages that contain the word "darn":
88///
89/// ```no_run
90/// # #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> {
91/// use twilight_http::Client;
92/// use twilight_model::{guild::auto_moderation::AutoModerationEventType, id::Id};
93///
94/// let client = Client::new("my token".to_owned());
95///
96/// let guild_id = Id::new(1);
97/// client
98/// .create_auto_moderation_rule(guild_id, "no darns", AutoModerationEventType::MessageSend)
99/// .action_block_message()
100/// .enabled(true)
101/// .with_keyword(&["darn"], &["d(?:4|a)rn"], &["darn it"])
102/// .await?;
103/// # Ok(()) }
104/// ```
105///
106/// [`MANAGE_GUILD`]: twilight_model::guild::Permissions::MANAGE_GUILD
107#[must_use = "requests must be configured and executed"]
108pub struct CreateAutoModerationRule<'a> {
109 fields: Result<CreateAutoModerationRuleFields<'a>, ValidationError>,
110 guild_id: Id<GuildMarker>,
111 http: &'a Client,
112 reason: Result<Option<&'a str>, ValidationError>,
113}
114
115impl<'a> CreateAutoModerationRule<'a> {
116 pub(crate) const fn new(
117 http: &'a Client,
118 guild_id: Id<GuildMarker>,
119 name: &'a str,
120 event_type: AutoModerationEventType,
121 ) -> Self {
122 Self {
123 fields: Ok(CreateAutoModerationRuleFields {
124 actions: None,
125 enabled: None,
126 event_type,
127 exempt_channels: None,
128 exempt_roles: None,
129 name,
130 trigger_metadata: None,
131 trigger_type: None,
132 }),
133 guild_id,
134 http,
135 reason: Ok(None),
136 }
137 }
138
139 /// Append an action of type [`BlockMessage`].
140 ///
141 /// [`BlockMessage`]: AutoModerationActionType::BlockMessage
142 pub fn action_block_message(mut self) -> Self {
143 self.fields = self.fields.map(|mut fields| {
144 fields.actions.get_or_insert_with(Vec::new).push(
145 CreateAutoModerationRuleFieldsAction {
146 kind: AutoModerationActionType::BlockMessage,
147 metadata: CreateAutoModerationRuleFieldsActionMetadata::default(),
148 },
149 );
150
151 fields
152 });
153
154 self
155 }
156
157 /// Append an action of type [`BlockMessage`] with an explanation for blocking messages.
158 ///
159 /// # Errors
160 ///
161 /// Returns a [`ValidationErrorType::AutoModerationBlockActionCustomMessageLimit`] if the custom message length
162 /// is invalid.
163 ///
164 /// [`ValidationErrorType::AutoModerationBlockActionCustomMessageLimit`]: twilight_validate::request::ValidationErrorType::AutoModerationBlockActionCustomMessageLimit
165 /// [`BlockMessage`]: AutoModerationActionType::BlockMessage
166 pub fn action_block_message_with_explanation(mut self, custom_message: &'a str) -> Self {
167 self.fields = self.fields.and_then(|mut fields| {
168 validate_auto_moderation_block_action_custom_message_limit(custom_message)?;
169 fields.actions.get_or_insert_with(Vec::new).push(
170 CreateAutoModerationRuleFieldsAction {
171 kind: AutoModerationActionType::BlockMessage,
172 metadata: CreateAutoModerationRuleFieldsActionMetadata {
173 custom_message: Some(String::from(custom_message)),
174 ..Default::default()
175 },
176 },
177 );
178
179 Ok(fields)
180 });
181
182 self
183 }
184
185 /// Append an action of type [`SendAlertMessage`].
186 ///
187 /// [`SendAlertMessage`]: AutoModerationActionType::SendAlertMessage
188 pub fn action_send_alert_message(mut self, channel_id: Id<ChannelMarker>) -> Self {
189 self.fields = self.fields.map(|mut fields| {
190 fields.actions.get_or_insert_with(Vec::new).push(
191 CreateAutoModerationRuleFieldsAction {
192 kind: AutoModerationActionType::SendAlertMessage,
193 metadata: CreateAutoModerationRuleFieldsActionMetadata {
194 channel_id: Some(channel_id),
195 ..Default::default()
196 },
197 },
198 );
199
200 fields
201 });
202
203 self
204 }
205
206 /// Append an action of type [`Timeout`].
207 ///
208 /// # Errors
209 ///
210 /// Returns [`ValidationErrorType::AutoModerationActionMetadataDurationSeconds`] if the duration
211 /// is invalid.
212 ///
213 /// [`Timeout`]: AutoModerationActionType::Timeout
214 /// [`ValidationErrorType::AutoModerationActionMetadataDurationSeconds`]: twilight_validate::request::ValidationErrorType::AutoModerationActionMetadataDurationSeconds
215 pub fn action_timeout(mut self, duration_seconds: u32) -> Self {
216 self.fields = self.fields.and_then(|mut fields| {
217 validate_auto_moderation_action_metadata_duration_seconds(duration_seconds)?;
218 fields.actions.get_or_insert_with(Vec::new).push(
219 CreateAutoModerationRuleFieldsAction {
220 kind: AutoModerationActionType::Timeout,
221 metadata: CreateAutoModerationRuleFieldsActionMetadata {
222 duration_seconds: Some(duration_seconds),
223 ..Default::default()
224 },
225 },
226 );
227
228 Ok(fields)
229 });
230
231 self
232 }
233
234 /// Set whether the rule is enabled.
235 pub fn enabled(mut self, enabled: bool) -> Self {
236 self.fields = self.fields.map(|mut fields| {
237 fields.enabled = Some(enabled);
238
239 fields
240 });
241
242 self
243 }
244
245 /// Set the channels where the rule does not apply.
246 /// See [Discord Docs/Trigger Metadata].
247 ///
248 /// # Errors
249 ///
250 /// Returns [`ValidationErrorType::AutoModerationExemptChannels`] if the `exempt_roles` field is invalid.
251 ///
252 /// [Discord Docs/Trigger Metadata]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata
253 /// [`ValidationErrorType::AutoModerationExemptChannels`]: twilight_validate::request::ValidationErrorType::AutoModerationExemptChannels
254 pub fn exempt_channels(mut self, exempt_channels: &'a [Id<ChannelMarker>]) -> Self {
255 self.fields = self.fields.and_then(|mut fields| {
256 validate_auto_moderation_exempt_channels(exempt_channels)?;
257 fields.exempt_channels = Some(exempt_channels);
258
259 Ok(fields)
260 });
261
262 self
263 }
264
265 /// Set the roles to which the rule does not apply.
266 /// See [Discord Docs/Trigger Metadata].
267 ///
268 /// # Errors
269 ///
270 /// Returns [`ValidationErrorType::AutoModerationExemptRoles`] if the `exempt_roles` field is invalid.
271 ///
272 /// [Discord Docs/Trigger Metadata]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata
273 /// [`ValidationErrorType::AutoModerationExemptRoles`]: twilight_validate::request::ValidationErrorType::AutoModerationExemptRoles
274 pub fn exempt_roles(mut self, exempt_roles: &'a [Id<RoleMarker>]) -> Self {
275 self.fields = self.fields.and_then(|mut fields| {
276 validate_auto_moderation_exempt_roles(exempt_roles)?;
277 fields.exempt_roles = Some(exempt_roles);
278
279 Ok(fields)
280 });
281
282 self
283 }
284
285 /// Create the request with the trigger type [`Keyword`], then execute it.
286 ///
287 /// Rules of this type require the `keyword_filter`, `regex_patterns` and
288 /// `allow_list` fields specified, and this method ensures this.
289 /// See [Discord Docs/Keyword Matching Strategies] and
290 /// [Discord Docs/Trigger Metadata] for more information.
291 ///
292 /// Only rust-flavored regex is currently supported by Discord.
293 ///
294 /// # Errors
295 ///
296 /// Returns [`ValidationErrorType::AutoModerationMetadataKeywordFilter`] if the `keyword_filter`
297 /// field is invalid.
298 ///
299 /// Returns [`ValidationErrorType::AutoModerationMetadataKeywordFilterItem`] if a `keyword_filter`
300 /// item is invalid.
301 ///
302 /// Returns [`ValidationErrorType::AutoModerationMetadataAllowList`] if the `allow_list` field is
303 /// invalid.
304 ///
305 /// Returns [`ValidationErrorType::AutoModerationMetadataAllowListItem`] if an `allow_list` item
306 /// is invalid.
307 ///
308 /// Returns [`ValidationErrorType::AutoModerationMetadataRegexPatterns`] if the `regex_patterns`
309 /// field is invalid.
310 ///
311 /// Returns [`ValidationErrorType::AutoModerationMetadataRegexPatternsItem`] if a `regex_patterns`
312 /// item is invalid.
313 ///
314 /// [`Keyword`]: AutoModerationTriggerType::Keyword
315 /// [Discord Docs/Keyword Matching Strategies]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-matching-strategies
316 /// [Discord Docs/Trigger Metadata]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata
317 /// [`ValidationErrorType::AutoModerationMetadataKeywordFilter`]: twilight_validate::request::ValidationErrorType::AutoModerationMetadataKeywordFilter
318 /// [`ValidationErrorType::AutoModerationMetadataKeywordFilterItem`]: twilight_validate::request::ValidationErrorType::AutoModerationMetadataKeywordFilterItem
319 /// [`ValidationErrorType::AutoModerationMetadataAllowList`]: twilight_validate::request::ValidationErrorType::AutoModerationMetadataAllowList
320 /// [`ValidationErrorType::AutoModerationMetadataAllowListItem`]: twilight_validate::request::ValidationErrorType::AutoModerationMetadataAllowListItem
321 /// [`ValidationErrorType::AutoModerationMetadataRegexPatterns`]: twilight_validate::request::ValidationErrorType::AutoModerationMetadataRegexPatterns
322 /// [`ValidationErrorType::AutoModerationMetadataRegexPatternsItem`]: twilight_validate::request::ValidationErrorType::AutoModerationMetadataRegexPatternsItem
323 pub fn with_keyword(
324 mut self,
325 keyword_filter: &'a [&'a str],
326 regex_patterns: &'a [&'a str],
327 allow_list: &'a [&'a str],
328 ) -> ResponseFuture<AutoModerationRule> {
329 self.fields = self.fields.and_then(|mut fields| {
330 validate_auto_moderation_metadata_keyword_allow_list(allow_list)?;
331 validate_auto_moderation_metadata_keyword_filter(keyword_filter)?;
332 validate_auto_moderation_metadata_regex_patterns(regex_patterns)?;
333 fields.trigger_metadata = Some(CreateAutoModerationRuleFieldsTriggerMetadata {
334 allow_list: Some(allow_list),
335 keyword_filter: Some(keyword_filter),
336 presets: None,
337 mention_total_limit: None,
338 regex_patterns: Some(regex_patterns),
339 });
340
341 fields.trigger_type = Some(AutoModerationTriggerType::Keyword);
342
343 Ok(fields)
344 });
345
346 self.exec()
347 }
348
349 /// Create the request with the trigger type [`Spam`], then execute it.
350 ///
351 /// [`Spam`]: AutoModerationTriggerType::Spam
352 pub fn with_spam(mut self) -> ResponseFuture<AutoModerationRule> {
353 self.fields = self.fields.map(|mut fields| {
354 fields.trigger_type = Some(AutoModerationTriggerType::Spam);
355
356 fields
357 });
358
359 self.exec()
360 }
361
362 /// Create the request with the trigger type [`KeywordPreset`], then execute
363 /// it.
364 ///
365 /// Rules of this type require the `presets` and `allow_list` fields
366 /// specified, and this method ensures this. See [Discord Docs/TriggerMetadata].
367 ///
368 /// # Errors
369 ///
370 /// Returns [`ValidationErrorType::AutoModerationMetadataPresetAllowList`] if the `allow_list` is
371 /// invalid.
372 ///
373 /// Returns [`ValidationErrorType::AutoModerationMetadataPresetAllowListItem`] if a `allow_list`
374 /// item is invalid.
375 ///
376 /// [`KeywordPreset`]: AutoModerationTriggerType::KeywordPreset
377 /// [Discord Docs/Trigger Metadata]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata
378 /// [`ValidationErrorType::AutoModerationMetadataPresetAllowList`]: twilight_validate::request::ValidationErrorType::AutoModerationMetadataPresetAllowList
379 /// [`ValidationErrorType::AutoModerationMetadataPresetAllowListItem`]: twilight_validate::request::ValidationErrorType::AutoModerationMetadataPresetAllowListItem
380 pub fn with_keyword_preset(
381 mut self,
382 presets: &'a [AutoModerationKeywordPresetType],
383 allow_list: &'a [&'a str],
384 ) -> ResponseFuture<AutoModerationRule> {
385 self.fields = self.fields.and_then(|mut fields| {
386 validate_auto_moderation_metadata_keyword_allow_list(allow_list)?;
387 fields.trigger_metadata = Some(CreateAutoModerationRuleFieldsTriggerMetadata {
388 allow_list: Some(allow_list),
389 keyword_filter: None,
390 presets: Some(presets),
391 mention_total_limit: None,
392 regex_patterns: None,
393 });
394
395 fields.trigger_type = Some(AutoModerationTriggerType::KeywordPreset);
396
397 Ok(fields)
398 });
399
400 self.exec()
401 }
402
403 /// Create the request with the trigger type [`MentionSpam`], then execute
404 /// it.
405 ///
406 /// Rules of this type requires the `mention_total_limit` field specified,
407 /// and this method ensures this. See [Discord Docs/Trigger Metadata].
408 ///
409 /// # Errors
410 ///
411 /// Returns a [`ValidationErrorType::AutoModerationMetadataMentionTotalLimit`] if `mention_total_limit`
412 /// is invalid.
413 ///
414 /// [`MentionSpam`]: AutoModerationTriggerType::MentionSpam
415 /// [Discord Docs/Trigger Metadata]: https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-trigger-metadata
416 /// [`ValidationErrorType::AutoModerationMetadataMentionTotalLimit`]: twilight_validate::request::ValidationErrorType::AutoModerationMetadataMentionTotalLimit
417 pub fn with_mention_spam(
418 mut self,
419 mention_total_limit: u8,
420 ) -> ResponseFuture<AutoModerationRule> {
421 self.fields = self.fields.and_then(|mut fields| {
422 validate_auto_moderation_metadata_mention_total_limit(mention_total_limit)?;
423 fields.trigger_metadata = Some(CreateAutoModerationRuleFieldsTriggerMetadata {
424 allow_list: None,
425 keyword_filter: None,
426 presets: None,
427 mention_total_limit: Some(mention_total_limit),
428 regex_patterns: None,
429 });
430 fields.trigger_type = Some(AutoModerationTriggerType::MentionSpam);
431
432 Ok(fields)
433 });
434
435 self.exec()
436 }
437
438 /// Execute the request, returning a future resolving to a [`Response`].
439 ///
440 /// [`Response`]: crate::response::Response
441 fn exec(self) -> ResponseFuture<AutoModerationRule> {
442 let http = self.http;
443
444 match self.try_into_request() {
445 Ok(request) => http.request(request),
446 Err(source) => ResponseFuture::error(source),
447 }
448 }
449}
450
451impl<'a> AuditLogReason<'a> for CreateAutoModerationRule<'a> {
452 fn reason(mut self, reason: &'a str) -> Self {
453 self.reason = validate_audit_reason(reason).and(Ok(Some(reason)));
454
455 self
456 }
457}
458
459impl TryIntoRequest for CreateAutoModerationRule<'_> {
460 fn try_into_request(self) -> Result<Request, HttpError> {
461 let fields = self.fields.map_err(HttpError::validation)?;
462 let mut request = Request::builder(&Route::CreateAutoModerationRule {
463 guild_id: self.guild_id.get(),
464 })
465 .json(&fields);
466
467 if let Some(reason) = self.reason.map_err(HttpError::validation)? {
468 request = request.headers(request::audit_header(reason)?);
469 }
470
471 request.build()
472 }
473}