Skip to main content

serenity/builder/
edit_role.rs

1#[cfg(feature = "http")]
2use super::Builder;
3use super::CreateAttachment;
4#[cfg(feature = "http")]
5use crate::http::CacheHttp;
6#[cfg(feature = "http")]
7use crate::internal::prelude::*;
8use crate::model::prelude::*;
9
10/// A builder to create or edit a [`Role`] for use via a number of model methods.
11///
12/// These are:
13///
14/// - [`PartialGuild::create_role`]
15/// - [`PartialGuild::edit_role`]
16/// - [`Guild::create_role`]
17/// - [`Guild::edit_role`]
18/// - [`GuildId::create_role`]
19/// - [`GuildId::edit_role`]
20/// - [`Role::edit`]
21///
22/// Defaults are provided for each parameter on role creation.
23///
24/// # Examples
25///
26/// Create a hoisted, mentionable role named `"a test role"`:
27///
28/// ```rust,no_run
29/// # use serenity::builder::EditRole;
30/// # use serenity::http::Http;
31/// # use serenity::model::id::GuildId;
32/// # use std::sync::Arc;
33/// #
34/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
35/// # let http: Arc<Http> = unimplemented!();
36/// # let guild_id: GuildId = unimplemented!();
37/// #
38/// // assuming a `guild_id` has been bound
39/// let builder = EditRole::new().name("a test role").hoist(true).mentionable(true);
40/// let role = guild_id.create_role(&http, builder).await?;
41/// # Ok(())
42/// # }
43/// ```
44///
45/// [Discord docs](https://discord.com/developers/docs/resources/guild#modify-guild-role)
46#[derive(Clone, Debug, Default, Serialize)]
47#[must_use]
48pub struct EditRole<'a> {
49    #[serde(skip_serializing_if = "Option::is_none")]
50    name: Option<String>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    permissions: Option<u64>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    #[serde(rename = "color")]
55    colour: Option<Colour>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    #[serde(rename = "colors")]
58    colours: Option<CreateRoleColours>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    hoist: Option<bool>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    icon: Option<Option<String>>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    unicode_emoji: Option<Option<String>>,
65
66    #[serde(skip_serializing_if = "Option::is_none")]
67    mentionable: Option<bool>,
68
69    #[serde(skip)]
70    position: Option<u16>,
71    #[serde(skip)]
72    audit_log_reason: Option<&'a str>,
73}
74
75impl<'a> EditRole<'a> {
76    /// Equivalent to [`Self::default`].
77    pub fn new() -> Self {
78        Self::default()
79    }
80
81    /// Creates a new builder with the values of the given [`Role`].
82    pub fn from_role(role: &Role) -> Self {
83        EditRole {
84            hoist: Some(role.hoist),
85            mentionable: Some(role.mentionable),
86            name: Some(role.name.clone()),
87            permissions: Some(role.permissions.bits()),
88            position: Some(role.position),
89            colour: Some(role.colour),
90            unicode_emoji: role.unicode_emoji.as_ref().map(|v| Some(v.clone())),
91            audit_log_reason: None,
92            colours: Some(role.colours.into()),
93            // TODO: Do we want to download role.icon?
94            icon: None,
95        }
96    }
97
98    /// Set the colour of the role.
99    pub fn colour(mut self, colour: impl Into<Colour>) -> Self {
100        self.colour = Some(colour.into());
101        self
102    }
103
104    /// Sets the colours of the role. Supports gradient and holographic role colours.
105    pub fn colours(mut self, colours: impl Into<CreateRoleColours>) -> Self {
106        self.colours = Some(colours.into());
107        self
108    }
109
110    /// Whether or not to hoist the role above lower-positioned roles in the user list.
111    pub fn hoist(mut self, hoist: bool) -> Self {
112        self.hoist = Some(hoist);
113        self
114    }
115
116    /// Whether or not to make the role mentionable, upon which users with that role will be
117    /// notified.
118    pub fn mentionable(mut self, mentionable: bool) -> Self {
119        self.mentionable = Some(mentionable);
120        self
121    }
122
123    /// Set the role's name.
124    pub fn name(mut self, name: impl Into<String>) -> Self {
125        self.name = Some(name.into());
126        self
127    }
128
129    /// Set the role's permissions.
130    pub fn permissions(mut self, permissions: Permissions) -> Self {
131        self.permissions = Some(permissions.bits());
132        self
133    }
134
135    /// Set the role's position in the role list. This correlates to the role's position in the
136    /// user list.
137    pub fn position(mut self, position: u16) -> Self {
138        self.position = Some(position);
139        self
140    }
141
142    /// Set the role icon to a unicode emoji.
143    pub fn unicode_emoji(mut self, unicode_emoji: Option<String>) -> Self {
144        self.unicode_emoji = Some(unicode_emoji);
145        self.icon = Some(None);
146        self
147    }
148
149    /// Set the role icon to a custom image.
150    pub fn icon(mut self, icon: Option<&CreateAttachment>) -> Self {
151        self.icon = Some(icon.map(CreateAttachment::to_base64));
152        self.unicode_emoji = Some(None);
153        self
154    }
155
156    /// Sets the request's audit log reason.
157    pub fn audit_log_reason(mut self, reason: &'a str) -> Self {
158        self.audit_log_reason = Some(reason);
159        self
160    }
161}
162
163/// The colours of a Discord role, secondary_colour and tertiary_colour may only be set if
164/// the [Guild] has the `ENHANCED_ROLE_COLORS` feature.
165///
166/// Note: 2024-07-05 - tertiary_colour is currently enforced to be set with a specific pair of
167/// primary and secondary colours, for current validation see
168/// [Discord docs](https://discord.com/developers/docs/topics/permissions#role-object-role-colors-object).
169#[derive(Clone, Debug, Default, Serialize)]
170#[must_use]
171#[allow(clippy::struct_field_names)]
172pub struct CreateRoleColours {
173    primary_color: Colour,
174    #[serde(skip_serializing_if = "Option::is_none")]
175    secondary_color: Option<Colour>,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    tertiary_color: Option<Colour>,
178}
179
180impl CreateRoleColours {
181    pub fn new(primary_colour: Colour) -> Self {
182        Self {
183            primary_color: primary_colour,
184            secondary_color: None,
185            tertiary_color: None,
186        }
187    }
188
189    /// Sets the secondary colour for this role.
190    pub fn secondary_colour(mut self, secondary_colour: Colour) -> Self {
191        self.secondary_color = Some(secondary_colour);
192        self
193    }
194
195    /// Sets the tertiary colour for this role, see struct documentation for limitations.
196    pub fn tertiary_colour(mut self, tertiary_colour: Colour) -> Self {
197        self.tertiary_color = Some(tertiary_colour);
198        self
199    }
200}
201
202impl From<RoleColours> for CreateRoleColours {
203    fn from(c: RoleColours) -> CreateRoleColours {
204        CreateRoleColours {
205            primary_color: c.primary_colour,
206            secondary_color: c.secondary_colour,
207            tertiary_color: c.tertiary_colour,
208        }
209    }
210}
211
212#[cfg(feature = "http")]
213#[async_trait::async_trait]
214impl Builder for EditRole<'_> {
215    type Context<'ctx> = (GuildId, Option<RoleId>);
216    type Built = Role;
217
218    /// Edits the role.
219    ///
220    /// **Note**: Requires the [Manage Roles] permission.
221    ///
222    /// # Errors
223    ///
224    /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
225    /// lacks permission. Otherwise returns [`Error::Http`], as well as if invalid data is given.
226    ///
227    /// [Manage Roles]: Permissions::MANAGE_ROLES
228    async fn execute(
229        self,
230        cache_http: impl CacheHttp,
231        ctx: Self::Context<'_>,
232    ) -> Result<Self::Built> {
233        let (guild_id, role_id) = ctx;
234
235        #[cfg(feature = "cache")]
236        crate::utils::user_has_guild_perms(&cache_http, guild_id, Permissions::MANAGE_ROLES)?;
237
238        let http = cache_http.http();
239        let role = match role_id {
240            Some(role_id) => {
241                http.edit_role(guild_id, role_id, &self, self.audit_log_reason).await?
242            },
243            None => http.create_role(guild_id, &self, self.audit_log_reason).await?,
244        };
245
246        if let Some(position) = self.position {
247            http.edit_role_position(guild_id, role.id, position, self.audit_log_reason).await?;
248        }
249        Ok(role)
250    }
251}