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