Skip to main content

twilight_http/request/guild/
update_guild.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    guild::{
13        DefaultMessageNotificationLevel, ExplicitContentFilter, PartialGuild, SystemChannelFlags,
14        VerificationLevel,
15    },
16    id::{
17        Id,
18        marker::{ChannelMarker, GuildMarker, UserMarker},
19    },
20};
21use twilight_validate::request::{
22    ValidationError, audit_reason as validate_audit_reason, guild_name as validate_guild_name,
23};
24
25#[derive(Serialize)]
26struct UpdateGuildFields<'a> {
27    #[serde(skip_serializing_if = "Option::is_none")]
28    afk_channel_id: Option<Nullable<Id<ChannelMarker>>>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    afk_timeout: Option<u64>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    banner: Option<Nullable<&'a str>>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    default_message_notifications: Option<Nullable<DefaultMessageNotificationLevel>>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    discovery_splash: Option<Nullable<&'a str>>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    explicit_content_filter: Option<Nullable<ExplicitContentFilter>>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    features: Option<&'a [&'a str]>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    icon: Option<Nullable<&'a str>>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    name: Option<&'a str>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    owner_id: Option<Id<UserMarker>>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    splash: Option<Nullable<&'a str>>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    system_channel_id: Option<Nullable<Id<ChannelMarker>>>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    system_channel_flags: Option<Nullable<SystemChannelFlags>>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    verification_level: Option<Nullable<VerificationLevel>>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    rules_channel_id: Option<Nullable<Id<ChannelMarker>>>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    public_updates_channel_id: Option<Nullable<Id<ChannelMarker>>>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    preferred_locale: Option<Nullable<&'a str>>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    premium_progress_bar_enabled: Option<bool>,
63}
64
65/// Update a guild.
66///
67/// All endpoints are optional. See [Discord Docs/Modify Guild].
68///
69/// [Discord Docs/Modify Guild]: https://discord.com/developers/docs/resources/guild#modify-guild
70#[must_use = "requests must be configured and executed"]
71pub struct UpdateGuild<'a> {
72    fields: Result<UpdateGuildFields<'a>, ValidationError>,
73    guild_id: Id<GuildMarker>,
74    http: &'a Client,
75    reason: Result<Option<&'a str>, ValidationError>,
76}
77
78impl<'a> UpdateGuild<'a> {
79    pub(crate) const fn new(http: &'a Client, guild_id: Id<GuildMarker>) -> Self {
80        Self {
81            fields: Ok(UpdateGuildFields {
82                afk_channel_id: None,
83                afk_timeout: None,
84                banner: None,
85                default_message_notifications: None,
86                discovery_splash: None,
87                explicit_content_filter: None,
88                features: None,
89                icon: None,
90                name: None,
91                owner_id: None,
92                splash: None,
93                system_channel_id: None,
94                system_channel_flags: None,
95                verification_level: None,
96                rules_channel_id: None,
97                public_updates_channel_id: None,
98                preferred_locale: None,
99                premium_progress_bar_enabled: None,
100            }),
101            guild_id,
102            http,
103            reason: Ok(None),
104        }
105    }
106
107    /// Set the voice channel where AFK voice users are sent.
108    pub const fn afk_channel_id(mut self, afk_channel_id: Option<Id<ChannelMarker>>) -> Self {
109        if let Ok(fields) = self.fields.as_mut() {
110            fields.afk_channel_id = Some(Nullable(afk_channel_id));
111        }
112
113        self
114    }
115
116    /// Set how much time it takes for a voice user to be considered AFK.
117    pub const fn afk_timeout(mut self, afk_timeout: u64) -> Self {
118        if let Ok(fields) = self.fields.as_mut() {
119            fields.afk_timeout = Some(afk_timeout);
120        }
121
122        self
123    }
124
125    /// Set the banner.
126    ///
127    /// This is a base64 encoded 16:9 PNG or JPEG image. Pass `None` to remove
128    /// the banner.
129    ///
130    /// The server must have the `BANNER` feature.
131    pub const fn banner(mut self, banner: Option<&'a str>) -> Self {
132        if let Ok(fields) = self.fields.as_mut() {
133            fields.banner = Some(Nullable(banner));
134        }
135
136        self
137    }
138
139    /// Set the default message notification level. See
140    /// [Discord Docs/Create Guild] for more information.
141    ///
142    /// [Discord Docs/Create Guild]: https://discord.com/developers/docs/resources/guild#create-guild
143    pub const fn default_message_notifications(
144        mut self,
145        default_message_notifications: Option<DefaultMessageNotificationLevel>,
146    ) -> Self {
147        if let Ok(fields) = self.fields.as_mut() {
148            fields.default_message_notifications = Some(Nullable(default_message_notifications));
149        }
150
151        self
152    }
153
154    /// Set the guild's discovery splash image.
155    ///
156    /// Requires the guild to have the `DISCOVERABLE` feature enabled.
157    pub const fn discovery_splash(mut self, discovery_splash: Option<&'a str>) -> Self {
158        if let Ok(fields) = self.fields.as_mut() {
159            fields.discovery_splash = Some(Nullable(discovery_splash));
160        }
161
162        self
163    }
164
165    /// Set the explicit content filter level.
166    pub const fn explicit_content_filter(
167        mut self,
168        explicit_content_filter: Option<ExplicitContentFilter>,
169    ) -> Self {
170        if let Ok(fields) = self.fields.as_mut() {
171            fields.explicit_content_filter = Some(Nullable(explicit_content_filter));
172        }
173
174        self
175    }
176
177    /// Set the enabled features of the guild.
178    ///
179    /// Attempting to add or remove the [`GuildFeature::Community`] feature requires the
180    /// [`Permissions::ADMINISTRATOR`] permission.
181    ///
182    /// Attempting to add or remove the [`GuildFeature::Discoverable`] feature requires
183    /// the [`Permissions::ADMINISTRATOR`] permission. Additionally the guild
184    /// must pass all the discovery requirements.
185    ///
186    /// Attempting to add or remove the [`GuildFeature::InvitesDisabled`] feature requires
187    /// the [`Permissions::MANAGE_GUILD`] permission.
188    ///
189    /// [`GuildFeature::Community`]: twilight_model::guild::GuildFeature::Community
190    /// [`GuildFeature::Discoverable`]: twilight_model::guild::GuildFeature::Discoverable
191    /// [`GuildFeature::InvitesDisabled`]: twilight_model::guild::GuildFeature::InvitesDisabled
192    /// [`Permissions::ADMINISTRATOR`]: twilight_model::guild::Permissions::ADMINISTRATOR
193    /// [`Permissions::MANAGE_GUILD`]: twilight_model::guild::Permissions::MANAGE_GUILD
194    pub const fn features(mut self, features: &'a [&'a str]) -> Self {
195        if let Ok(fields) = self.fields.as_mut() {
196            fields.features = Some(features);
197        }
198
199        self
200    }
201
202    /// Set the icon.
203    ///
204    /// This must be a Data URI, in the form of
205    /// `data:image/{type};base64,{data}` where `{type}` is the image MIME type
206    /// and `{data}` is the base64-encoded image. See [Discord Docs/Image Data].
207    ///
208    /// [Discord Docs/Image Data]: https://discord.com/developers/docs/reference#image-data
209    pub const fn icon(mut self, icon: Option<&'a str>) -> Self {
210        if let Ok(fields) = self.fields.as_mut() {
211            fields.icon = Some(Nullable(icon));
212        }
213
214        self
215    }
216
217    /// Set the name of the guild.
218    ///
219    /// The minimum length is 1 UTF-16 character and the maximum is 100 UTF-16
220    /// characters.
221    ///
222    /// # Errors
223    ///
224    /// Returns an error of type [`GuildName`] if the name length is too short
225    /// or too long.
226    ///
227    /// [`GuildName`]: twilight_validate::request::ValidationErrorType::GuildName
228    pub fn name(mut self, name: &'a str) -> Self {
229        self.fields = self.fields.and_then(|mut fields| {
230            validate_guild_name(name)?;
231            fields.name.replace(name);
232
233            Ok(fields)
234        });
235
236        self
237    }
238
239    /// Transfer ownership to another user.
240    ///
241    /// Only works if the current user is the owner.
242    pub const fn owner_id(mut self, owner_id: Id<UserMarker>) -> Self {
243        if let Ok(fields) = self.fields.as_mut() {
244            fields.owner_id = Some(owner_id);
245        }
246
247        self
248    }
249
250    /// Set the guild's splash image.
251    ///
252    /// Requires the guild to have the `INVITE_SPLASH` feature enabled.
253    pub const fn splash(mut self, splash: Option<&'a str>) -> Self {
254        if let Ok(fields) = self.fields.as_mut() {
255            fields.splash = Some(Nullable(splash));
256        }
257
258        self
259    }
260
261    /// Set the channel where events such as welcome messages are posted.
262    pub const fn system_channel(mut self, system_channel_id: Option<Id<ChannelMarker>>) -> Self {
263        if let Ok(fields) = self.fields.as_mut() {
264            fields.system_channel_id = Some(Nullable(system_channel_id));
265        }
266
267        self
268    }
269
270    /// Set the guild's [`SystemChannelFlags`].
271    pub const fn system_channel_flags(
272        mut self,
273        system_channel_flags: Option<SystemChannelFlags>,
274    ) -> Self {
275        if let Ok(fields) = self.fields.as_mut() {
276            fields.system_channel_flags = Some(Nullable(system_channel_flags));
277        }
278
279        self
280    }
281
282    /// Set the rules channel.
283    ///
284    /// Requires the guild to be `PUBLIC`. See [Discord Docs/Modify Guild].
285    ///
286    /// [Discord Docs/Modify Guild]: https://discord.com/developers/docs/resources/guild#modify-guild
287    pub const fn rules_channel(mut self, rules_channel_id: Option<Id<ChannelMarker>>) -> Self {
288        if let Ok(fields) = self.fields.as_mut() {
289            fields.rules_channel_id = Some(Nullable(rules_channel_id));
290        }
291
292        self
293    }
294
295    /// Set the public updates channel.
296    ///
297    /// Requires the guild to be `PUBLIC`.
298    pub const fn public_updates_channel(
299        mut self,
300        public_updates_channel_id: Option<Id<ChannelMarker>>,
301    ) -> Self {
302        if let Ok(fields) = self.fields.as_mut() {
303            fields.public_updates_channel_id = Some(Nullable(public_updates_channel_id));
304        }
305
306        self
307    }
308
309    /// Set the preferred locale for the guild.
310    ///
311    /// Defaults to `en-US`. Requires the guild to be `PUBLIC`.
312    pub const fn preferred_locale(mut self, preferred_locale: Option<&'a str>) -> Self {
313        if let Ok(fields) = self.fields.as_mut() {
314            fields.preferred_locale = Some(Nullable(preferred_locale));
315        }
316
317        self
318    }
319
320    /// Set the verification level.
321    ///
322    /// See [Discord Docs/Guild Object].
323    ///
324    /// [Discord Docs/Guild Object]: https://discord.com/developers/docs/resources/guild#guild-object-verification-level
325    pub const fn verification_level(
326        mut self,
327        verification_level: Option<VerificationLevel>,
328    ) -> Self {
329        if let Ok(fields) = self.fields.as_mut() {
330            fields.verification_level = Some(Nullable(verification_level));
331        }
332
333        self
334    }
335
336    /// Set whether the premium progress bar is enabled.
337    pub const fn premium_progress_bar_enabled(
338        mut self,
339        premium_progress_bar_enabled: bool,
340    ) -> Self {
341        if let Ok(fields) = self.fields.as_mut() {
342            fields.premium_progress_bar_enabled = Some(premium_progress_bar_enabled);
343        }
344
345        self
346    }
347}
348
349impl<'a> AuditLogReason<'a> for UpdateGuild<'a> {
350    fn reason(mut self, reason: &'a str) -> Self {
351        self.reason = validate_audit_reason(reason).and(Ok(Some(reason)));
352
353        self
354    }
355}
356
357#[cfg(not(target_os = "wasi"))]
358impl IntoFuture for UpdateGuild<'_> {
359    type Output = Result<Response<PartialGuild>, Error>;
360
361    type IntoFuture = ResponseFuture<PartialGuild>;
362
363    fn into_future(self) -> Self::IntoFuture {
364        let http = self.http;
365
366        match self.try_into_request() {
367            Ok(request) => http.request(request),
368            Err(source) => ResponseFuture::error(source),
369        }
370    }
371}
372
373impl TryIntoRequest for UpdateGuild<'_> {
374    fn try_into_request(self) -> Result<Request, Error> {
375        let fields = self.fields.map_err(Error::validation)?;
376        let mut request = Request::builder(&Route::UpdateGuild {
377            guild_id: self.guild_id.get(),
378        })
379        .json(&fields);
380
381        if let Some(reason) = self.reason.map_err(Error::validation)? {
382            request = request.headers(request::audit_header(reason)?);
383        }
384
385        request.build()
386    }
387}