Skip to main content

twilight_http/request/channel/
update_channel.rs

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