twilight_http/request/channel/
update_channel.rs

1use crate::{
2    client::Client,
3    error::Error,
4    request::{self, AuditLogReason, Nullable, Request, TryIntoRequest},
5    response::{Response, ResponseFuture},
6    routing::Route,
7};
8use serde::Serialize;
9use std::future::IntoFuture;
10use twilight_model::{
11    channel::{
12        forum::{DefaultReaction, ForumLayout, ForumSortOrder, ForumTag},
13        permission_overwrite::PermissionOverwrite,
14        Channel, ChannelFlags, ChannelType, VideoQualityMode,
15    },
16    id::{marker::ChannelMarker, Id},
17};
18use twilight_validate::{
19    channel::{
20        bitrate as validate_bitrate, forum_topic as validate_forum_topic, name as validate_name,
21        topic as validate_topic, user_limit as validate_user_limit, ChannelValidationError,
22    },
23    request::{audit_reason as validate_audit_reason, ValidationError},
24};
25
26// The Discord API doesn't require the `name` and `kind` fields to be present,
27// but it does require them to be non-null.
28#[derive(Serialize)]
29struct UpdateChannelFields<'a> {
30    #[serde(skip_serializing_if = "Option::is_none")]
31    available_tags: Option<&'a [ForumTag]>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    bitrate: Option<u32>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    default_forum_layout: Option<ForumLayout>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    default_reaction_emoji: Option<Nullable<&'a DefaultReaction>>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    default_sort_order: Option<Nullable<ForumSortOrder>>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    default_thread_rate_limit_per_user: Option<Nullable<u16>>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    flags: Option<ChannelFlags>,
44    #[serde(rename = "type")]
45    #[serde(skip_serializing_if = "Option::is_none")]
46    kind: Option<ChannelType>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    name: Option<&'a str>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    nsfw: Option<bool>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    parent_id: Option<Nullable<Id<ChannelMarker>>>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    permission_overwrites: Option<&'a [PermissionOverwrite]>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    position: Option<u64>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    rate_limit_per_user: Option<u16>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    rtc_region: Option<Nullable<&'a str>>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    topic: Option<&'a str>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    user_limit: Option<u16>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    video_quality_mode: Option<VideoQualityMode>,
67}
68
69/// Update a channel.
70///
71/// All fields are optional. The minimum length of the name is 1 UTF-16 character
72/// and the maximum is 100 UTF-16 characters.
73#[must_use = "requests must be configured and executed"]
74pub struct UpdateChannel<'a> {
75    channel_id: Id<ChannelMarker>,
76    fields: Result<UpdateChannelFields<'a>, ChannelValidationError>,
77    http: &'a Client,
78    reason: Result<Option<&'a str>, ValidationError>,
79}
80
81impl<'a> UpdateChannel<'a> {
82    pub(crate) const fn new(http: &'a Client, channel_id: Id<ChannelMarker>) -> Self {
83        Self {
84            channel_id,
85            fields: Ok(UpdateChannelFields {
86                available_tags: None,
87                bitrate: None,
88                default_forum_layout: None,
89                default_reaction_emoji: None,
90                default_sort_order: None,
91                default_thread_rate_limit_per_user: None,
92                flags: None,
93                kind: None,
94                name: None,
95                nsfw: None,
96                parent_id: None,
97                permission_overwrites: None,
98                position: None,
99                rate_limit_per_user: None,
100                rtc_region: None,
101                topic: None,
102                user_limit: None,
103                video_quality_mode: None,
104            }),
105            http,
106            reason: Ok(None),
107        }
108    }
109
110    /// Set the available tags for the forum.
111    pub fn available_tags(mut self, available_tags: &'a [ForumTag]) -> Self {
112        if let Ok(fields) = self.fields.as_mut() {
113            fields.available_tags = Some(available_tags);
114        }
115
116        self
117    }
118
119    /// For voice and stage channels, set the bitrate of the channel.
120    ///
121    /// Must be at least 8000.
122    ///
123    /// # Errors
124    ///
125    /// Returns an error of type [`BitrateInvalid`] if the bitrate is invalid.
126    ///
127    /// [`BitrateInvalid`]: twilight_validate::channel::ChannelValidationErrorType::BitrateInvalid
128    pub fn bitrate(mut self, bitrate: u32) -> Self {
129        self.fields = self.fields.and_then(|mut fields| {
130            validate_bitrate(bitrate)?;
131            fields.bitrate = Some(bitrate);
132
133            Ok(fields)
134        });
135
136        self
137    }
138
139    /// Set the default layout for forum channels.
140    pub fn default_forum_layout(mut self, default_forum_layout: ForumLayout) -> Self {
141        if let Ok(fields) = self.fields.as_mut() {
142            fields.default_forum_layout = Some(default_forum_layout);
143        }
144
145        self
146    }
147
148    /// Set the default reaction emoji for new forum threads.
149    pub fn default_reaction_emoji(
150        mut self,
151        default_reaction_emoji: Option<&'a DefaultReaction>,
152    ) -> Self {
153        if let Ok(fields) = self.fields.as_mut() {
154            fields.default_reaction_emoji = Some(Nullable(default_reaction_emoji));
155        }
156
157        self
158    }
159
160    /// Set the default sort order for forum channels.
161    pub fn default_sort_order(mut self, default_sort_order: Option<ForumSortOrder>) -> Self {
162        if let Ok(fields) = self.fields.as_mut() {
163            fields.default_sort_order = Some(Nullable(default_sort_order));
164        }
165
166        self
167    }
168
169    /// Set the default number of seconds that a user must wait before before
170    /// they are able to send another message in new forum threads.
171    ///
172    /// The minimum is 0 and the maximum is 21600. This is also known as "Slow
173    /// Mode". See [Discord Docs/Channel Object].
174    ///
175    /// # Errors
176    ///
177    /// Returns an error of type [`RateLimitPerUserInvalid`] if the limit is
178    /// invalid.
179    ///
180    /// [`RateLimitPerUserInvalid`]: twilight_validate::channel::ChannelValidationErrorType::RateLimitPerUserInvalid
181    /// [Discord Docs/Channel Object]: https://discordapp.com/developers/docs/resources/channel#channel-object-channel-structure
182    pub fn default_thread_rate_limit_per_user(
183        mut self,
184        default_thread_rate_limit_per_user: Option<u16>,
185    ) -> Self {
186        self.fields = self.fields.and_then(|mut fields| {
187            if let Some(default_thread_rate_limit_per_user) = default_thread_rate_limit_per_user {
188                twilight_validate::channel::rate_limit_per_user(
189                    default_thread_rate_limit_per_user,
190                )?;
191            }
192
193            fields.default_thread_rate_limit_per_user =
194                Some(Nullable(default_thread_rate_limit_per_user));
195
196            Ok(fields)
197        });
198
199        self
200    }
201
202    /// Set the flags of the channel, if supported.
203    pub fn flags(mut self, flags: ChannelFlags) -> Self {
204        if let Ok(fields) = self.fields.as_mut() {
205            fields.flags = Some(flags);
206        }
207
208        self
209    }
210
211    /// Set the forum topic.
212    ///
213    /// The maximum length is 4096 UTF-16 characters. See
214    /// [Discord Docs/Channel Object].
215    ///
216    /// # Errors
217    ///
218    /// Returns an error of type [`ForumTopicInvalid`] if the channel type is
219    /// [`GuildForum`] and the topic is invalid.
220    ///
221    /// [Discord Docs/Channel Object]: https://discordapp.com/developers/docs/resources/channel#channel-object-channel-structure
222    /// [`ForumTopicInvalid`]: twilight_validate::channel::ChannelValidationErrorType::ForumTopicInvalid
223    /// [`GuildForum`]: twilight_model::channel::ChannelType::GuildForum
224    pub fn forum_topic(mut self, topic: Option<&'a str>) -> Self {
225        self.fields = self.fields.and_then(|mut fields| {
226            if let Some(topic) = topic {
227                validate_forum_topic(topic)?;
228            }
229
230            fields.topic = topic;
231
232            Ok(fields)
233        });
234
235        self
236    }
237
238    /// Set the name.
239    ///
240    /// The minimum length is 1 UTF-16 character and the maximum is 100 UTF-16
241    /// characters.
242    ///
243    /// # Errors
244    ///
245    /// Returns an error of type [`NameInvalid`] if the name is invalid.
246    ///
247    /// [`NameInvalid`]: twilight_validate::channel::ChannelValidationErrorType::NameInvalid
248    pub fn name(mut self, name: &'a str) -> Self {
249        self.fields = self.fields.and_then(|mut fields| {
250            validate_name(name)?;
251            fields.name = Some(name);
252
253            Ok(fields)
254        });
255
256        self
257    }
258
259    /// Set whether the channel is marked as NSFW.
260    pub fn nsfw(mut self, nsfw: bool) -> Self {
261        if let Ok(fields) = self.fields.as_mut() {
262            fields.nsfw = Some(nsfw);
263        }
264
265        self
266    }
267
268    /// If this is specified, and the parent ID is a `ChannelType::CategoryChannel`, move this
269    /// channel to a child of the category channel.
270    pub fn parent_id(mut self, parent_id: Option<Id<ChannelMarker>>) -> Self {
271        if let Ok(fields) = self.fields.as_mut() {
272            fields.parent_id = Some(Nullable(parent_id));
273        }
274
275        self
276    }
277
278    /// Set the permission overwrites of a channel. This will overwrite all permissions that the
279    /// channel currently has, so use with caution!
280    pub fn permission_overwrites(
281        mut self,
282        permission_overwrites: &'a [PermissionOverwrite],
283    ) -> Self {
284        if let Ok(fields) = self.fields.as_mut() {
285            fields.permission_overwrites = Some(permission_overwrites);
286        }
287
288        self
289    }
290
291    /// Set the position of the channel.
292    ///
293    /// Positions are numerical and zero-indexed. If you place a channel at position 2, channels
294    /// 2-n will shift down one position and the initial channel will take its place.
295    pub fn position(mut self, position: u64) -> Self {
296        if let Ok(fields) = self.fields.as_mut() {
297            fields.position = Some(position);
298        }
299
300        self
301    }
302
303    /// Set the number of seconds that a user must wait before before they are able to send another
304    /// message.
305    ///
306    /// The minimum is 0 and the maximum is 21600. This is also known as "Slow
307    /// Mode". See [Discord Docs/Channel Object].
308    ///
309    /// # Errors
310    ///
311    /// Returns an error of type [`RateLimitPerUserInvalid`] if the limit is
312    /// invalid.
313    ///
314    /// [`RateLimitPerUserInvalid`]: twilight_validate::channel::ChannelValidationErrorType::RateLimitPerUserInvalid
315    /// [Discord Docs/Channel Object]: https://discordapp.com/developers/docs/resources/channel#channel-object-channel-structure
316    pub fn rate_limit_per_user(mut self, rate_limit_per_user: u16) -> Self {
317        self.fields = self.fields.and_then(|mut fields| {
318            twilight_validate::channel::rate_limit_per_user(rate_limit_per_user)?;
319            fields.rate_limit_per_user = Some(rate_limit_per_user);
320
321            Ok(fields)
322        });
323
324        self
325    }
326
327    /// For voice and stage channels, set the channel's RTC region.
328    ///
329    /// Set to `None` to clear.
330    pub fn rtc_region(mut self, rtc_region: Option<&'a str>) -> Self {
331        if let Ok(fields) = self.fields.as_mut() {
332            fields.rtc_region = Some(Nullable(rtc_region));
333        }
334
335        self
336    }
337
338    /// Set the topic.
339    ///
340    /// The maximum length is 1024 UTF-16 characters. See
341    /// [Discord Docs/Channel Object].
342    ///
343    /// # Errors
344    ///
345    /// Returns an error of type [`TopicInvalid`] if the topic is invalid.
346    ///
347    /// [Discord Docs/Channel Object]: https://discordapp.com/developers/docs/resources/channel#channel-object-channel-structure
348    /// [`TopicInvalid`]: twilight_validate::channel::ChannelValidationErrorType::TopicInvalid
349    pub fn topic(mut self, topic: &'a str) -> Self {
350        self.fields = self.fields.and_then(|mut fields| {
351            validate_topic(topic)?;
352            fields.topic.replace(topic);
353
354            Ok(fields)
355        });
356
357        self
358    }
359
360    /// For voice channels, set the user limit.
361    ///
362    /// Set to 0 for no limit. Limit can otherwise be between 1 and 99
363    /// inclusive. See [Discord Docs/Modify Channel].
364    ///
365    /// # Errors
366    ///
367    /// Returns an error of type [`UserLimitInvalid`] if the bitrate is invalid.
368    ///
369    /// [Discord Docs/Modify Channel]: https://discord.com/developers/docs/resources/channel#modify-channel-json-params-guild-channel
370    /// [`UserLimitInvalid`]: twilight_validate::channel::ChannelValidationErrorType::UserLimitInvalid
371    pub fn user_limit(mut self, user_limit: u16) -> Self {
372        self.fields = self.fields.and_then(|mut fields| {
373            validate_user_limit(user_limit)?;
374            fields.user_limit = Some(user_limit);
375
376            Ok(fields)
377        });
378
379        self
380    }
381
382    /// Set the [`VideoQualityMode`] for the voice channel.
383    pub fn video_quality_mode(mut self, video_quality_mode: VideoQualityMode) -> Self {
384        if let Ok(fields) = self.fields.as_mut() {
385            fields.video_quality_mode = Some(video_quality_mode);
386        }
387
388        self
389    }
390
391    /// Set the kind of channel.
392    ///
393    /// Only conversion between `ChannelType::GuildText` and
394    /// `ChannelType::GuildAnnouncement` is possible, and only if the guild has the
395    /// `NEWS` feature enabled. See [Discord Docs/Modify Channel].
396    ///
397    /// [Discord Docs/Modify Channel]: https://discord.com/developers/docs/resources/channel#modify-channel-json-params-guild-channel
398    pub fn kind(mut self, kind: ChannelType) -> Self {
399        if let Ok(fields) = self.fields.as_mut() {
400            fields.kind = Some(kind);
401        }
402
403        self
404    }
405}
406
407impl<'a> AuditLogReason<'a> for UpdateChannel<'a> {
408    fn reason(mut self, reason: &'a str) -> Self {
409        self.reason = validate_audit_reason(reason).and(Ok(Some(reason)));
410
411        self
412    }
413}
414
415impl IntoFuture for UpdateChannel<'_> {
416    type Output = Result<Response<Channel>, Error>;
417
418    type IntoFuture = ResponseFuture<Channel>;
419
420    fn into_future(self) -> Self::IntoFuture {
421        let http = self.http;
422
423        match self.try_into_request() {
424            Ok(request) => http.request(request),
425            Err(source) => ResponseFuture::error(source),
426        }
427    }
428}
429
430impl TryIntoRequest for UpdateChannel<'_> {
431    fn try_into_request(self) -> Result<Request, Error> {
432        let fields = self.fields.map_err(Error::validation)?;
433        let mut request = Request::builder(&Route::UpdateChannel {
434            channel_id: self.channel_id.get(),
435        })
436        .json(&fields);
437
438        if let Some(reason) = self.reason.map_err(Error::validation)? {
439            request = request.headers(request::audit_header(reason)?);
440        }
441
442        request.build()
443    }
444}