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