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}