serenity/model/sticker.rs
1#[cfg(feature = "model")]
2use crate::builder::EditSticker;
3#[cfg(feature = "model")]
4use crate::http::{CacheHttp, Http};
5#[cfg(feature = "model")]
6use crate::internal::prelude::*;
7use crate::model::prelude::*;
8use crate::model::utils::comma_separated_string;
9
10#[cfg(feature = "model")]
11impl StickerPackId {
12 /// Gets the [`StickerPack`] object.
13 ///
14 /// # Errors
15 ///
16 /// Returns [`Error::Http`] if a [`StickerPack`] with that [`StickerPackId`] does not exist, or
17 /// is otherwise unavailable.
18 pub async fn to_sticker_pack(self, http: impl AsRef<Http>) -> Result<StickerPack> {
19 http.as_ref().get_sticker_pack(self).await
20 }
21}
22
23#[cfg(feature = "model")]
24impl StickerId {
25 /// Delete a guild sticker.
26 ///
27 /// **Note**: If the sticker was created by the current user, requires either the [Create Guild
28 /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild
29 /// Expressions] permission is required.
30 ///
31 /// # Errors
32 ///
33 /// Returns [`Error::Http`] if the current user lacks permission.
34 ///
35 /// [Create Guild Expressions]: Permissions::CREATE_GUILD_EXPRESSIONS
36 /// [Manage Guild Expressions]: Permissions::MANAGE_GUILD_EXPRESSIONS
37 #[deprecated = "use `GuildId::delete_sticker` instead"]
38 pub async fn delete(self, http: impl AsRef<Http>, guild_id: impl Into<GuildId>) -> Result<()> {
39 guild_id.into().delete_sticker(http, self).await
40 }
41
42 /// Requests the sticker via the REST API to get a [`Sticker`] with all details.
43 ///
44 /// # Errors
45 ///
46 /// Returns [`Error::Http`] if a [`Sticker`] with that [`StickerId`] does not exist, or is
47 /// otherwise unavailable.
48 pub async fn to_sticker(self, http: impl AsRef<Http>) -> Result<Sticker> {
49 http.as_ref().get_sticker(self).await
50 }
51
52 /// Edits the sticker.
53 ///
54 /// **Note**: If the sticker was created by the current user, requires either the [Create Guild
55 /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild
56 /// Expressions] permission is required.
57 ///
58 /// # Errors
59 ///
60 /// Returns [`Error::Http`] if the current user lacks permission, or if invalid data is given.
61 ///
62 /// [Create Guild Expressions]: Permissions::CREATE_GUILD_EXPRESSIONS
63 /// [Manage Guild Expressions]: Permissions::MANAGE_GUILD_EXPRESSIONS
64 #[deprecated = "use `GuildId::edit_sticker` instead"]
65 pub async fn edit(
66 self,
67 cache_http: impl CacheHttp,
68 guild_id: impl Into<GuildId>,
69 builder: EditSticker<'_>,
70 ) -> Result<Sticker> {
71 guild_id.into().edit_sticker(cache_http, self, builder).await
72 }
73}
74
75/// The smallest amount of data required to render a sticker.
76///
77/// [Discord docs](https://discord.com/developers/docs/resources/sticker#sticker-item-object).
78#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
79#[derive(Clone, Debug, Deserialize, Serialize)]
80#[non_exhaustive]
81pub struct StickerItem {
82 /// The unique ID given to this sticker.
83 pub id: StickerId,
84 /// The name of the sticker.
85 pub name: String,
86 /// The type of sticker format.
87 pub format_type: StickerFormatType,
88}
89
90#[cfg(feature = "model")]
91impl StickerItem {
92 /// Requests the sticker via the REST API to get a [`Sticker`] with all details.
93 ///
94 /// # Errors
95 ///
96 /// Returns [`Error::Http`] if a [`Sticker`] with that [`StickerId`] does
97 /// not exist, or is otherwise unavailable.
98 #[inline]
99 pub async fn to_sticker(&self, http: impl AsRef<Http>) -> Result<Sticker> {
100 self.id.to_sticker(http).await
101 }
102
103 /// Retrieves the URL to the sticker image.
104 ///
105 /// **Note**: This will only be `None` if the format_type is unknown.
106 #[inline]
107 #[must_use]
108 pub fn image_url(&self) -> Option<String> {
109 sticker_url(self.id, self.format_type)
110 }
111}
112
113/// A sticker sent with a message.
114///
115/// Bots currently can only receive messages with stickers, not send.
116///
117/// [Discord docs](https://discord.com/developers/docs/resources/sticker#sticker-pack-object).
118#[derive(Clone, Debug, Deserialize, Serialize)]
119#[non_exhaustive]
120pub struct StickerPack {
121 /// The unique ID given to this sticker sticker pack.
122 pub id: StickerPackId,
123 /// The stickers in the pack
124 pub stickers: Vec<Sticker>,
125 /// The name of the sticker pack
126 pub name: String,
127 /// The unique ID given to the pack's SKU.
128 pub sku_id: SkuId,
129 /// ID of a sticker in the pack which is shown as the pack's icon.
130 pub cover_sticker_id: Option<StickerId>,
131 /// Description of the sticker pack.
132 pub description: String,
133 /// The unique ID given to the sticker pack's banner image.
134 pub banner_asset_id: StickerPackBannerId,
135}
136
137#[cfg(feature = "model")]
138impl StickerPack {
139 /// Returns the sticker that is shown as the pack's icon
140 #[must_use]
141 pub fn cover_sticker(&self) -> Option<&Sticker> {
142 self.cover_sticker_id.and_then(|id| self.stickers.iter().find(|s| s.id == id))
143 }
144
145 #[must_use]
146 pub fn banner_url(&self) -> String {
147 banner_url(self.banner_asset_id)
148 }
149}
150
151#[cfg(feature = "model")]
152fn banner_url(banner_asset_id: StickerPackBannerId) -> String {
153 cdn!("/app-assets/710982414301790216/store/{}.webp?size=1024", banner_asset_id)
154}
155
156/// A sticker sent with a message.
157///
158/// [Discord docs](https://discord.com/developers/docs/resources/sticker#sticker-object).
159#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
160#[derive(Clone, Debug, Deserialize, Serialize)]
161#[non_exhaustive]
162pub struct Sticker {
163 /// The unique ID given to this sticker.
164 pub id: StickerId,
165 /// The unique ID of the pack the sticker is from.
166 pub pack_id: Option<StickerPackId>,
167 /// The name of the sticker.
168 pub name: String,
169 /// Description of the sticker
170 pub description: Option<String>,
171 /// For guild stickers, the Discord name of a unicode emoji representing the sticker's
172 /// expression. For standard stickers, a list of related expressions.
173 #[serde(with = "comma_separated_string")]
174 pub tags: Vec<String>,
175 /// The type of sticker.
176 #[serde(rename = "type")]
177 pub kind: StickerType,
178 /// The type of sticker format.
179 pub format_type: StickerFormatType,
180 /// Whether or not this guild sticker can be used, may be false due to loss of Server Boosts.
181 #[serde(default)]
182 pub available: bool,
183 /// Id of the guild that owns this sticker.
184 pub guild_id: Option<GuildId>,
185 /// User that uploaded the sticker. This will be `None` if the current user does not have
186 /// either the [Create Guild Expressions] nor the [Manage Guild Expressions] permission.
187 ///
188 /// [Create Guild Expressions]: Permissions::CREATE_GUILD_EXPRESSIONS
189 /// [Manage Guild Expressions]: Permissions::MANAGE_GUILD_EXPRESSIONS
190 pub user: Option<User>,
191 /// A sticker's sort order within a pack.
192 pub sort_value: Option<u16>,
193}
194
195#[cfg(feature = "model")]
196impl Sticker {
197 /// Deletes the [`Sticker`] from its guild.
198 ///
199 /// **Note**: If the sticker was created by the current user, requires either the [Create Guild
200 /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild
201 /// Expressions] permission is required.
202 ///
203 /// # Errors
204 ///
205 /// Returns [`Error::Http`] if the current user lacks permission to delete the sticker.
206 ///
207 /// [Create Guild Expressions]: Permissions::CREATE_GUILD_EXPRESSIONS
208 /// [Manage Guild Expressions]: Permissions::MANAGE_GUILD_EXPRESSIONS
209 #[inline]
210 pub async fn delete(&self, http: impl AsRef<Http>) -> Result<()> {
211 if let Some(guild_id) = self.guild_id {
212 guild_id.delete_sticker(http, self.id).await
213 } else {
214 Err(Error::Model(ModelError::DeleteNitroSticker))
215 }
216 }
217
218 /// Edits the sticker.
219 ///
220 /// **Note**: If the sticker was created by the current user, requires either the [Create Guild
221 /// Expressions] or the [Manage Guild Expressions] permission. Otherwise, the [Manage Guild
222 /// Expressions] permission is required.
223 ///
224 /// # Examples
225 ///
226 /// Rename a sticker:
227 ///
228 /// ```rust,no_run
229 /// # use serenity::http::Http;
230 /// # use serenity::model::id::GuildId;
231 /// # use serenity::model::sticker::Sticker;
232 /// use serenity::builder::EditSticker;
233 ///
234 /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
235 /// # let http: Http = unimplemented!();
236 /// # let mut sticker: Sticker = unimplemented!();
237 /// let builder = EditSticker::new().name("Bun bun meow");
238 /// sticker.edit(&http, builder).await?;
239 /// # Ok(())
240 /// # }
241 /// ```
242 ///
243 /// # Errors
244 ///
245 /// Returns [`Error::Http`] if the current user lacks permission.
246 ///
247 /// [Create Guild Expressions]: Permissions::CREATE_GUILD_EXPRESSIONS
248 /// [Manage Guild Expressions]: Permissions::MANAGE_GUILD_EXPRESSIONS
249 #[inline]
250 pub async fn edit(
251 &mut self,
252 cache_http: impl CacheHttp,
253 builder: EditSticker<'_>,
254 ) -> Result<()> {
255 if let Some(guild_id) = self.guild_id {
256 *self = guild_id.edit_sticker(cache_http, self.id, builder).await?;
257 Ok(())
258 } else {
259 Err(Error::Model(ModelError::DeleteNitroSticker))
260 }
261 }
262
263 /// Retrieves the URL to the sticker image.
264 ///
265 /// **Note**: This will only be `None` if the format_type is unknown.
266 #[inline]
267 #[must_use]
268 pub fn image_url(&self) -> Option<String> {
269 sticker_url(self.id, self.format_type)
270 }
271}
272
273enum_number! {
274 /// Differentiates between sticker types.
275 ///
276 /// [Discord docs](https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-types).
277 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
278 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
279 #[serde(from = "u8", into = "u8")]
280 #[non_exhaustive]
281 pub enum StickerType {
282 /// An official sticker in a pack, part of Nitro or in a removed purchasable pack.
283 Standard = 1,
284 /// A sticker uploaded to a Boosted guild for the guild's members.
285 Guild = 2,
286 _ => Unknown(u8),
287 }
288}
289
290enum_number! {
291 /// Differentiates between sticker formats.
292 ///
293 /// [Discord docs](https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types).
294 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
295 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
296 #[serde(from = "u8", into = "u8")]
297 #[non_exhaustive]
298 pub enum StickerFormatType {
299 /// A PNG format sticker.
300 Png = 1,
301 /// An APNG format animated sticker.
302 Apng = 2,
303 /// A LOTTIE format animated sticker.
304 Lottie = 3,
305 /// A GIF format animated sticker.
306 Gif = 4,
307 _ => Unknown(u8),
308 }
309}
310
311#[cfg(feature = "model")]
312fn sticker_url(sticker_id: StickerId, sticker_format_type: StickerFormatType) -> Option<String> {
313 let ext = match sticker_format_type {
314 StickerFormatType::Png | StickerFormatType::Apng => "png",
315 StickerFormatType::Lottie => "json",
316 StickerFormatType::Gif => "gif",
317 StickerFormatType::Unknown(_) => return None,
318 };
319
320 Some(cdn!("/stickers/{}.{}", sticker_id, ext))
321}