serenity/model/
invite.rs

1//! Models for server and channel invites.
2
3use super::prelude::*;
4#[cfg(feature = "model")]
5use crate::builder::CreateInvite;
6#[cfg(all(feature = "cache", feature = "model"))]
7use crate::cache::Cache;
8#[cfg(feature = "model")]
9use crate::http::{CacheHttp, Http};
10#[cfg(feature = "model")]
11use crate::internal::prelude::*;
12
13/// Information about an invite code.
14///
15/// Information can not be accessed for guilds the current user is banned from.
16///
17/// [Discord docs](https://discord.com/developers/docs/resources/invite#invite-object).
18#[derive(Clone, Debug, Deserialize, Serialize)]
19#[non_exhaustive]
20pub struct Invite {
21    /// The approximate number of [`Member`]s in the related [`Guild`].
22    pub approximate_member_count: Option<u64>,
23    /// The approximate number of [`Member`]s with an active session in the related [`Guild`].
24    ///
25    /// An active session is defined as an open, heartbeating WebSocket connection.
26    /// These include [invisible][`OnlineStatus::Invisible`] members.
27    pub approximate_presence_count: Option<u64>,
28    /// The unique code for the invite.
29    pub code: String,
30    /// A representation of the minimal amount of information needed about the [`GuildChannel`]
31    /// being invited to.
32    pub channel: InviteChannel,
33    /// A representation of the minimal amount of information needed about the [`Guild`] being
34    /// invited to.
35    pub guild: Option<InviteGuild>,
36    /// A representation of the minimal amount of information needed about the [`User`] that
37    /// created the invite.
38    ///
39    /// This can be [`None`] for invites created by Discord such as invite-widgets or vanity invite
40    /// links.
41    pub inviter: Option<User>,
42    /// The type of target for this voice channel invite.
43    pub target_type: Option<InviteTargetType>,
44    /// The user whose stream to display for this voice channel stream invite.
45    ///
46    /// Only shows up if `target_type` is `Stream`.
47    pub target_user: Option<UserId>,
48    /// The embedded application to open for this voice channel embedded application invite.
49    ///
50    /// Only shows up if `target_type` is `EmmbeddedApplication`.
51    pub target_application: Option<ApplicationId>,
52    /// The expiration date of this invite, returned from `Http::get_invite` when `with_expiration`
53    /// is true.
54    pub expires_at: Option<Timestamp>,
55    /// The Stage instance data if there is a public Stage instance in the Stage channel this
56    /// invite is for.
57    pub stage_instance: Option<InviteStageInstance>,
58    /// Guild scheduled event data, only included if guild_scheduled_event_id contains a valid
59    /// guild scheduled event id (according to Discord docs, whatever that means).
60    #[serde(rename = "guild_scheduled_event")]
61    pub scheduled_event: Option<ScheduledEvent>,
62}
63
64#[cfg(feature = "model")]
65impl Invite {
66    /// Creates an invite for the given channel.
67    ///
68    /// **Note**: Requires the [Create Instant Invite] permission.
69    ///
70    /// # Errors
71    ///
72    /// If the `cache` is enabled, returns [`ModelError::InvalidPermissions`] if the current user
73    /// lacks permission. Otherwise returns [`Error::Http`], as well as if invalid data is given.
74    ///
75    /// [Create Instant Invite]: Permissions::CREATE_INSTANT_INVITE
76    #[inline]
77    pub async fn create(
78        cache_http: impl CacheHttp,
79        channel_id: impl Into<ChannelId>,
80        builder: CreateInvite<'_>,
81    ) -> Result<RichInvite> {
82        channel_id.into().create_invite(cache_http, builder).await
83    }
84
85    /// Deletes the invite.
86    ///
87    /// **Note**: Requires the [Manage Guild] permission.
88    ///
89    /// # Errors
90    ///
91    /// If the `cache` is enabled, returns a [`ModelError::InvalidPermissions`] if the current user
92    /// does not have the required [permission].
93    ///
94    /// Otherwise may return [`Error::Http`] if permissions are lacking, or if the invite is
95    /// invalid.
96    ///
97    /// [Manage Guild]: Permissions::MANAGE_GUILD
98    /// [permission]: super::permissions
99    pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<Invite> {
100        #[cfg(feature = "cache")]
101        {
102            if let Some(cache) = cache_http.cache() {
103                crate::utils::user_has_perms_cache(
104                    cache,
105                    self.channel.id,
106                    Permissions::MANAGE_GUILD,
107                )?;
108            }
109        }
110
111        cache_http.http().as_ref().delete_invite(&self.code, None).await
112    }
113
114    /// Gets information about an invite.
115    ///
116    /// # Arguments
117    /// * `code` - The invite code.
118    /// * `member_counts` - Whether to include information about the current number of members in
119    ///   the server that the invite belongs to.
120    /// * `expiration` - Whether to include information about when the invite expires.
121    /// * `event_id` - An optional server event ID to include with the invite.
122    ///
123    /// More information about these arguments can be found on Discord's
124    /// [API documentation](https://discord.com/developers/docs/resources/invite#get-invite).
125    ///
126    /// # Errors
127    ///
128    /// May return an [`Error::Http`] if the invite is invalid. Can also return an [`Error::Json`]
129    /// if there is an error deserializing the API response.
130    pub async fn get(
131        http: impl AsRef<Http>,
132        code: &str,
133        member_counts: bool,
134        expiration: bool,
135        event_id: Option<ScheduledEventId>,
136    ) -> Result<Invite> {
137        let mut invite = code;
138
139        #[cfg(feature = "utils")]
140        {
141            invite = crate::utils::parse_invite(invite);
142        }
143
144        http.as_ref().get_invite(invite, member_counts, expiration, event_id).await
145    }
146
147    /// Returns a URL to use for the invite.
148    ///
149    /// # Examples
150    ///
151    /// Retrieve the URL for an invite with the code `WxZumR`:
152    ///
153    /// ```rust
154    /// # use serenity::json::{json, from_value};
155    /// # use serenity::model::prelude::*;
156    /// #
157    /// # fn main() {
158    /// # let invite = from_value::<Invite>(json!({
159    /// #     "approximate_member_count": Some(1812),
160    /// #     "approximate_presence_count": Some(717),
161    /// #     "code": "WxZumR",
162    /// #     "channel": {
163    /// #         "id": ChannelId::new(1),
164    /// #         "name": "foo",
165    /// #         "type": ChannelType::Text,
166    /// #     },
167    /// #     "guild": {
168    /// #         "id": GuildId::new(2),
169    /// #         "icon": None::<String>,
170    /// #         "name": "bar",
171    /// #         "splash_hash": None::<String>,
172    /// #         "text_channel_count": 7,
173    /// #         "voice_channel_count": 3,
174    /// #         "features": ["NEWS", "DISCOVERABLE"],
175    /// #         "verification_level": 2,
176    /// #         "nsfw_level": 0,
177    /// #     },
178    /// #     "inviter": {
179    /// #         "id": UserId::new(3),
180    /// #         "username": "foo",
181    /// #         "discriminator": "1234",
182    /// #         "avatar": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
183    /// #     },
184    /// # })).unwrap();
185    /// #
186    /// assert_eq!(invite.url(), "https://discord.gg/WxZumR");
187    /// # }
188    /// ```
189    #[must_use]
190    pub fn url(&self) -> String {
191        format!("https://discord.gg/{}", self.code)
192    }
193}
194
195/// A minimal amount of information about the channel an invite points to.
196///
197/// [Discord docs](https://discord.com/developers/docs/resources/invite#invite-object-example-invite-object).
198#[non_exhaustive]
199#[derive(Clone, Debug, Deserialize, Serialize)]
200pub struct InviteChannel {
201    pub id: ChannelId,
202    pub name: String,
203    #[serde(rename = "type")]
204    pub kind: ChannelType,
205}
206
207/// Subset of [`Guild`] used in [`Invite`].
208///
209/// [Discord docs](https://discord.com/developers/docs/resources/invite#invite-object-example-invite-object).
210#[derive(Clone, Debug, Deserialize, Serialize)]
211#[non_exhaustive]
212pub struct InviteGuild {
213    pub id: GuildId,
214    pub name: String,
215    pub splash: Option<ImageHash>,
216    pub banner: Option<ImageHash>,
217    pub description: Option<String>,
218    pub icon: Option<ImageHash>,
219    pub features: Vec<String>,
220    pub verification_level: VerificationLevel,
221    pub vanity_url_code: Option<String>,
222    pub nsfw_level: NsfwLevel,
223    pub premium_subscription_count: Option<u64>,
224}
225
226#[cfg(feature = "model")]
227impl InviteGuild {
228    /// Returns the Id of the shard associated with the guild.
229    ///
230    /// When the cache is enabled this will automatically retrieve the total number of shards.
231    ///
232    /// **Note**: When the cache is enabled, this function unlocks the cache to retrieve the total
233    /// number of shards in use. If you already have the total, consider using [`utils::shard_id`].
234    ///
235    /// [`utils::shard_id`]: crate::utils::shard_id
236    #[cfg(all(feature = "cache", feature = "utils"))]
237    #[inline]
238    #[must_use]
239    pub fn shard_id(&self, cache: impl AsRef<Cache>) -> u32 {
240        self.id.shard_id(&cache)
241    }
242
243    /// Returns the Id of the shard associated with the guild.
244    ///
245    /// When the cache is enabled this will automatically retrieve the total number of shards.
246    ///
247    /// When the cache is not enabled, the total number of shards being used will need to be
248    /// passed.
249    ///
250    /// # Examples
251    ///
252    /// Retrieve the Id of the shard for a guild with Id `81384788765712384`, using 17 shards:
253    ///
254    /// ```rust,ignore
255    /// use serenity::utils;
256    ///
257    /// // assumes a `guild` has already been bound
258    ///
259    /// assert_eq!(guild.shard_id(17), 7);
260    /// ```
261    #[cfg(all(feature = "utils", not(feature = "cache")))]
262    #[inline]
263    #[must_use]
264    pub fn shard_id(&self, shard_count: u32) -> u32 {
265        self.id.shard_id(shard_count)
266    }
267}
268
269/// Detailed information about an invite.
270///
271/// This information can only be retrieved by anyone with the [Manage Guild] permission. Otherwise,
272/// a minimal amount of information can be retrieved via the [`Invite`] struct.
273///
274/// [Manage Guild]: Permissions::MANAGE_GUILD
275///
276/// [Discord docs](https://discord.com/developers/docs/resources/invite#invite-metadata-object) (extends [`Invite`] fields).
277#[derive(Clone, Debug, Deserialize, Serialize)]
278#[non_exhaustive]
279pub struct RichInvite {
280    /// A representation of the minimal amount of information needed about the channel being
281    /// invited to.
282    pub channel: InviteChannel,
283    /// The unique code for the invite.
284    pub code: String,
285    /// When the invite was created.
286    pub created_at: Timestamp,
287    /// A representation of the minimal amount of information needed about the [`Guild`] being
288    /// invited to.
289    pub guild: Option<InviteGuild>,
290    /// The user that created the invite.
291    pub inviter: Option<User>,
292    /// The maximum age of the invite in seconds, from when it was created.
293    pub max_age: u32,
294    /// The maximum number of times that an invite may be used before it expires.
295    ///
296    /// Note that this does not supersede the [`Self::max_age`] value, if the value of
297    /// [`Self::temporary`] is `true`. If the value of `temporary` is `false`, then the invite
298    /// _will_ self-expire after the given number of max uses.
299    ///
300    /// If the value is `0`, then the invite is permanent.
301    pub max_uses: u8,
302    /// Indicator of whether the invite self-expires after a certain amount of time or uses.
303    pub temporary: bool,
304    /// The amount of times that an invite has been used.
305    pub uses: u64,
306}
307
308#[cfg(feature = "model")]
309impl RichInvite {
310    /// Deletes the invite.
311    ///
312    /// Refer to [`Http::delete_invite`] for more information.
313    ///
314    /// **Note**: Requires the [Manage Guild] permission.
315    ///
316    /// # Errors
317    ///
318    /// If the `cache` feature is enabled, then this returns a [`ModelError::InvalidPermissions`]
319    /// if the current user does not have the required [permission].
320    ///
321    /// [Manage Guild]: Permissions::MANAGE_GUILD
322    /// [permission]: super::permissions
323    pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<Invite> {
324        #[cfg(feature = "cache")]
325        {
326            if let Some(cache) = cache_http.cache() {
327                crate::utils::user_has_perms_cache(
328                    cache,
329                    self.channel.id,
330                    Permissions::MANAGE_GUILD,
331                )?;
332            }
333        }
334
335        cache_http.http().as_ref().delete_invite(&self.code, None).await
336    }
337
338    /// Returns a URL to use for the invite.
339    ///
340    /// # Examples
341    ///
342    /// Retrieve the URL for an invite with the code `WxZumR`:
343    ///
344    /// ```rust
345    /// # use serenity::json::{json, from_value};
346    /// # use serenity::model::prelude::*;
347    /// #
348    /// # fn main() {
349    /// # let invite = from_value::<RichInvite>(json!({
350    /// #     "code": "WxZumR",
351    /// #     "channel": {
352    /// #         "id": ChannelId::new(1),
353    /// #         "name": "foo",
354    /// #         "type": ChannelType::Text,
355    /// #     },
356    /// #     "created_at": "2017-01-29T15:35:17.136000+00:00",
357    /// #     "guild": {
358    /// #         "id": GuildId::new(2),
359    /// #         "icon": None::<String>,
360    /// #         "name": "baz",
361    /// #         "splash_hash": None::<String>,
362    /// #         "text_channel_count": None::<u64>,
363    /// #         "voice_channel_count": None::<u64>,
364    /// #         "features": ["NEWS", "DISCOVERABLE"],
365    /// #         "verification_level": 2,
366    /// #         "nsfw_level": 0,
367    /// #     },
368    /// #     "inviter": {
369    /// #         "avatar": None::<String>,
370    /// #         "bot": false,
371    /// #         "discriminator": "1234",
372    /// #         "id": UserId::new(4),
373    /// #         "username": "qux",
374    /// #         "public_flags": None::<UserPublicFlags>,
375    /// #     },
376    /// #     "max_age": 5,
377    /// #     "max_uses": 6,
378    /// #     "temporary": true,
379    /// #     "uses": 7,
380    /// # })).unwrap();
381    /// #
382    /// assert_eq!(invite.url(), "https://discord.gg/WxZumR");
383    /// # }
384    /// ```
385    #[must_use]
386    pub fn url(&self) -> String {
387        format!("https://discord.gg/{}", self.code)
388    }
389}
390
391/// [Discord docs](https://discord.com/developers/docs/resources/invite#invite-stage-instance-object).
392#[derive(Clone, Debug, Deserialize, Serialize)]
393#[non_exhaustive]
394pub struct InviteStageInstance {
395    /// The members speaking in the Stage
396    pub members: Vec<PartialMember>,
397    /// The number of users in the Stage
398    pub participant_count: u64,
399    /// The number of users speaking in the Stage
400    pub speaker_count: u64,
401    /// The topic of the Stage instance (1-120 characters)
402    pub topic: String,
403}
404
405enum_number! {
406    /// Type of target for a voice channel invite.
407    ///
408    /// [Discord docs](https://discord.com/developers/docs/resources/invite#invite-object-invite-target-types).
409    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
410    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
411    #[serde(from = "u8", into = "u8")]
412    #[non_exhaustive]
413    pub enum InviteTargetType {
414        Stream = 1,
415        EmbeddedApplication = 2,
416        _ => Unknown(u8),
417    }
418}