serenity/model/channel/
mod.rs

1//! Models relating to channels and types within channels.
2
3mod attachment;
4mod channel_id;
5mod embed;
6mod guild_channel;
7mod message;
8mod partial_channel;
9mod private_channel;
10mod reaction;
11
12use std::fmt;
13
14use serde::de::{Error as DeError, Unexpected};
15use serde::ser::SerializeMap as _;
16
17pub use self::attachment::*;
18pub use self::channel_id::*;
19pub use self::embed::*;
20pub use self::guild_channel::*;
21pub use self::message::*;
22pub use self::partial_channel::*;
23pub use self::private_channel::*;
24pub use self::reaction::*;
25#[cfg(feature = "model")]
26use crate::http::CacheHttp;
27use crate::json::*;
28use crate::model::prelude::*;
29use crate::model::utils::is_false;
30
31#[deprecated = "use CreateAttachment instead"]
32#[cfg(feature = "model")]
33pub type AttachmentType<'a> = crate::builder::CreateAttachment;
34
35/// A container for any channel.
36#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
37#[derive(Clone, Debug, Serialize)]
38#[serde(untagged)]
39#[non_exhaustive]
40#[allow(clippy::large_enum_variant)] // https://github.com/rust-lang/rust-clippy/issues/9798
41pub enum Channel {
42    /// A channel within a [`Guild`].
43    Guild(GuildChannel),
44    /// A private channel to another [`User`] (Direct Message). No other users may access the
45    /// channel.
46    Private(PrivateChannel),
47}
48
49#[cfg(feature = "model")]
50impl Channel {
51    /// Converts from [`Channel`] to `Option<GuildChannel>`.
52    ///
53    /// Converts `self` into an `Option<GuildChannel>`, consuming `self`, and discarding a
54    /// [`PrivateChannel`] if any.
55    ///
56    /// # Examples
57    ///
58    /// Basic usage:
59    ///
60    /// ```rust,no_run
61    /// # use serenity::model::channel::Channel;
62    /// # fn run(channel: Channel) {
63    /// match channel.guild() {
64    ///     Some(guild_channel) => {
65    ///         println!("It's a guild channel named {}!", guild_channel.name);
66    ///     },
67    ///     None => {
68    ///         println!("It's not in a guild!");
69    ///     },
70    /// }
71    /// # }
72    /// ```
73    #[must_use]
74    pub fn guild(self) -> Option<GuildChannel> {
75        match self {
76            Self::Guild(lock) => Some(lock),
77            _ => None,
78        }
79    }
80
81    /// Converts from [`Channel`] to `Option<PrivateChannel>`.
82    ///
83    /// Converts `self` into an `Option<PrivateChannel>`, consuming `self`, and discarding a
84    /// [`GuildChannel`], if any.
85    ///
86    /// # Examples
87    ///
88    /// Basic usage:
89    ///
90    /// ```rust,no_run
91    /// # use serenity::model::channel::Channel;
92    /// # fn run(channel: Channel) {
93    /// #
94    /// match channel.private() {
95    ///     Some(private) => {
96    ///         println!("It's a private channel with {}!", &private.recipient);
97    ///     },
98    ///     None => {
99    ///         println!("It's not a private channel!");
100    ///     },
101    /// }
102    /// # }
103    /// ```
104    #[must_use]
105    pub fn private(self) -> Option<PrivateChannel> {
106        match self {
107            Self::Private(lock) => Some(lock),
108            _ => None,
109        }
110    }
111
112    /// If this is a category channel, returns it.
113    #[must_use]
114    pub fn category(self) -> Option<GuildChannel> {
115        match self {
116            Self::Guild(c) if c.kind == ChannelType::Category => Some(c),
117            _ => None,
118        }
119    }
120
121    /// Deletes the inner channel.
122    ///
123    /// # Errors
124    ///
125    /// If the `cache` is enabled, returns [`ModelError::InvalidPermissions`], if the current user
126    /// lacks permission.
127    ///
128    /// Otherwise will return [`Error::Http`] if the current user does not have permission.
129    pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<()> {
130        match self {
131            Self::Guild(public_channel) => {
132                public_channel.delete(cache_http).await?;
133            },
134            Self::Private(private_channel) => {
135                private_channel.delete(cache_http.http()).await?;
136            },
137        }
138
139        Ok(())
140    }
141
142    /// Determines if the channel is NSFW.
143    #[inline]
144    #[must_use]
145    #[cfg(feature = "model")]
146    #[deprecated = "Use the GuildChannel::nsfw field, as PrivateChannel is never NSFW"]
147    pub fn is_nsfw(&self) -> bool {
148        match self {
149            #[allow(deprecated)]
150            Self::Guild(channel) => channel.is_nsfw(),
151            Self::Private(_) => false,
152        }
153    }
154
155    /// Retrieves the Id of the inner [`GuildChannel`], or [`PrivateChannel`].
156    #[inline]
157    #[must_use]
158    pub const fn id(&self) -> ChannelId {
159        match self {
160            Self::Guild(ch) => ch.id,
161            Self::Private(ch) => ch.id,
162        }
163    }
164
165    /// Retrieves the position of the inner [`GuildChannel`].
166    ///
167    /// In DMs (private channel) it will return None.
168    #[inline]
169    #[must_use]
170    pub const fn position(&self) -> Option<u16> {
171        match self {
172            Self::Guild(channel) => Some(channel.position),
173            Self::Private(_) => None,
174        }
175    }
176}
177
178// Manual impl needed to emulate integer enum tags
179impl<'de> Deserialize<'de> for Channel {
180    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
181        let map = JsonMap::deserialize(deserializer)?;
182
183        let kind = {
184            let kind = map.get("type").ok_or_else(|| DeError::missing_field("type"))?;
185            kind.as_u64().ok_or_else(|| {
186                DeError::invalid_type(
187                    Unexpected::Other("non-positive integer"),
188                    &"a positive integer",
189                )
190            })?
191        };
192
193        let value = Value::from(map);
194        match kind {
195            0 | 2 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 => from_value(value).map(Channel::Guild),
196            1 => from_value(value).map(Channel::Private),
197            _ => return Err(DeError::custom("Unknown channel type")),
198        }
199        .map_err(DeError::custom)
200    }
201}
202
203impl fmt::Display for Channel {
204    /// Formats the channel into a "mentioned" string.
205    ///
206    /// This will return a different format for each type of channel:
207    /// - [`PrivateChannel`]s: the recipient's name;
208    /// - [`GuildChannel`]s: a string mentioning the channel that users who can see the channel can
209    ///   click on.
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        match self {
212            Self::Guild(ch) => fmt::Display::fmt(&ch.id.mention(), f),
213            Self::Private(ch) => fmt::Display::fmt(&ch.recipient.name, f),
214        }
215    }
216}
217
218enum_number! {
219    /// A representation of a type of channel.
220    ///
221    /// [Discord docs](https://discord.com/developers/docs/resources/channel#channel-object-channel-types).
222    #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
223    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
224    #[serde(from = "u8", into = "u8")]
225    #[non_exhaustive]
226    pub enum ChannelType {
227        /// An indicator that the channel is a text [`GuildChannel`].
228        #[default]
229        Text = 0,
230        /// An indicator that the channel is a [`PrivateChannel`].
231        Private = 1,
232        /// An indicator that the channel is a voice [`GuildChannel`].
233        Voice = 2,
234        /// An indicator that the channel is a group DM.
235        GroupDm = 3,
236        /// An indicator that the channel is a channel category.
237        Category = 4,
238        /// An indicator that the channel is a `NewsChannel`.
239        ///
240        /// Note: `NewsChannel` is serialized into a [`GuildChannel`]
241        News = 5,
242        /// An indicator that the channel is a news thread [`GuildChannel`].
243        NewsThread = 10,
244        /// An indicator that the channel is a public thread [`GuildChannel`].
245        PublicThread = 11,
246        /// An indicator that the channel is a private thread [`GuildChannel`].
247        PrivateThread = 12,
248        /// An indicator that the channel is a stage [`GuildChannel`].
249        Stage = 13,
250        /// An indicator that the channel is a directory [`GuildChannel`] in a [hub].
251        ///
252        /// [hub]: https://support.discord.com/hc/en-us/articles/4406046651927-Discord-Student-Hubs-FAQ
253        Directory = 14,
254        /// An indicator that the channel is a forum [`GuildChannel`].
255        Forum = 15,
256        _ => Unknown(u8),
257    } // Make sure to update [`GuildChannel::is_text_based`].
258}
259
260impl ChannelType {
261    #[inline]
262    #[must_use]
263    pub const fn name(&self) -> &str {
264        match *self {
265            Self::Private => "private",
266            Self::Text => "text",
267            Self::Voice => "voice",
268            Self::GroupDm => "group_dm",
269            Self::Category => "category",
270            Self::News => "news",
271            Self::NewsThread => "news_thread",
272            Self::PublicThread => "public_thread",
273            Self::PrivateThread => "private_thread",
274            Self::Stage => "stage",
275            Self::Directory => "directory",
276            Self::Forum => "forum",
277            Self::Unknown(_) => "unknown",
278        }
279    }
280}
281
282/// [Discord docs](https://discord.com/developers/docs/resources/channel#overwrite-object).
283#[derive(Clone, Debug, Deserialize, Serialize)]
284pub(crate) struct PermissionOverwriteData {
285    allow: Permissions,
286    deny: Permissions,
287    id: TargetId,
288    #[serde(rename = "type")]
289    kind: u8,
290}
291
292pub(crate) struct InvalidPermissionOverwriteType(u8);
293
294impl std::fmt::Display for InvalidPermissionOverwriteType {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        write!(f, "Invalid Permission Overwrite Type: {}", self.0)
297    }
298}
299
300impl std::convert::TryFrom<PermissionOverwriteData> for PermissionOverwrite {
301    type Error = InvalidPermissionOverwriteType;
302
303    fn try_from(data: PermissionOverwriteData) -> StdResult<Self, Self::Error> {
304        let kind = match data.kind {
305            0 => PermissionOverwriteType::Role(data.id.get().into()),
306            1 => PermissionOverwriteType::Member(data.id.into()),
307            raw => return Err(InvalidPermissionOverwriteType(raw)),
308        };
309
310        Ok(PermissionOverwrite {
311            allow: data.allow,
312            deny: data.deny,
313            kind,
314        })
315    }
316}
317
318impl From<PermissionOverwrite> for PermissionOverwriteData {
319    fn from(data: PermissionOverwrite) -> Self {
320        let (kind, id) = match data.kind {
321            PermissionOverwriteType::Role(id) => (0, id.get().into()),
322            PermissionOverwriteType::Member(id) => (1, id.into()),
323        };
324
325        PermissionOverwriteData {
326            allow: data.allow,
327            deny: data.deny,
328            kind,
329            id,
330        }
331    }
332}
333
334/// A channel-specific permission overwrite for a member or role.
335///
336/// [Discord docs](https://discord.com/developers/docs/resources/channel#overwrite-object).
337#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
338#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
339#[serde(try_from = "PermissionOverwriteData", into = "PermissionOverwriteData")]
340pub struct PermissionOverwrite {
341    pub allow: Permissions,
342    pub deny: Permissions,
343    pub kind: PermissionOverwriteType,
344}
345
346/// The type of edit being made to a Channel's permissions.
347///
348/// This is for use with methods such as [`GuildChannel::create_permission`].
349///
350/// If you would like to modify the default permissions of a channel, you can get its [`RoleId`]
351/// from [`GuildId::everyone_role`].
352///
353/// [Discord docs](https://discord.com/developers/docs/resources/channel#overwrite-object-overwrite-structure) (field `type`).
354#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
355#[derive(Clone, Copy, Debug, Eq, PartialEq)]
356#[non_exhaustive]
357pub enum PermissionOverwriteType {
358    /// A member which is having its permission overwrites edited.
359    Member(UserId),
360    /// A role which is having its permission overwrites edited.
361    Role(RoleId),
362}
363
364enum_number! {
365    /// The video quality mode for a voice channel.
366    ///
367    /// [Discord docs](https://discord.com/developers/docs/resources/channel#channel-object-video-quality-modes).
368    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
369    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
370    #[serde(from = "u8", into = "u8")]
371    #[non_exhaustive]
372    pub enum VideoQualityMode {
373        /// An indicator that the video quality is chosen by Discord for optimal
374        /// performance.
375        Auto = 1,
376        /// An indicator that the video quality is 720p.
377        Full = 2,
378        _ => Unknown(u8),
379    }
380}
381
382enum_number! {
383    /// See [`StageInstance::privacy_level`].
384    ///
385    /// [Discord docs](https://discord.com/developers/docs/resources/stage-instance#stage-instance-object-privacy-level).
386    #[derive(Clone, Copy, Default, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
387    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
388    #[serde(from = "u8", into = "u8")]
389    #[non_exhaustive]
390    pub enum StageInstancePrivacyLevel {
391        /// The Stage instance is visible publicly. (deprecated)
392        Public = 1,
393        /// The Stage instance is visible to only guild members.
394        #[default]
395        GuildOnly = 2,
396        _ => Unknown(u8),
397    }
398}
399
400enum_number! {
401    /// See [`ThreadMetadata::auto_archive_duration`].
402    ///
403    /// [Discord docs](https://discord.com/developers/docs/resources/channel#thread-metadata-object)
404    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
405    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
406    #[serde(from = "u16", into = "u16")]
407    #[non_exhaustive]
408    pub enum AutoArchiveDuration {
409        None = 0,
410        OneHour = 60,
411        OneDay = 1440,
412        ThreeDays = 4320,
413        OneWeek = 10080,
414        _ => Unknown(u16),
415    }
416}
417
418/// [Discord docs](https://discord.com/developers/docs/resources/stage-instance#stage-instance-object).
419#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
420#[derive(Clone, Debug, Deserialize, Serialize)]
421#[non_exhaustive]
422pub struct StageInstance {
423    /// The Id of the stage instance.
424    pub id: StageInstanceId,
425    /// The guild Id of the associated stage channel.
426    pub guild_id: GuildId,
427    /// The Id of the associated stage channel.
428    pub channel_id: ChannelId,
429    /// The topic of the stage instance.
430    pub topic: String,
431    /// The privacy level of the Stage instance.
432    pub privacy_level: StageInstancePrivacyLevel,
433    /// Whether or not Stage Discovery is disabled (deprecated).
434    pub discoverable_disabled: bool,
435    /// The id of the scheduled event for this Stage instance.
436    pub guild_scheduled_event_id: Option<ScheduledEventId>,
437}
438
439/// A thread data.
440///
441/// [Discord docs](https://discord.com/developers/docs/resources/channel#thread-metadata-object).
442#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
443#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
444#[non_exhaustive]
445pub struct ThreadMetadata {
446    /// Whether the thread is archived.
447    pub archived: bool,
448    /// Duration in minutes to automatically archive the thread after recent activity.
449    pub auto_archive_duration: AutoArchiveDuration,
450    /// The last time the thread's archive status was last changed; used for calculating recent
451    /// activity.
452    pub archive_timestamp: Option<Timestamp>,
453    /// When a thread is locked, only users with `MANAGE_THREADS` permission can unarchive it.
454    #[serde(default)]
455    pub locked: bool,
456    /// Timestamp when the thread was created.
457    ///
458    /// **Note**: only populated for threads created after 2022-01-09
459    pub create_timestamp: Option<Timestamp>,
460    /// Whether non-moderators can add other non-moderators to a thread.
461    ///
462    /// **Note**: Only available on private threads.
463    #[serde(default, skip_serializing_if = "is_false")]
464    pub invitable: bool,
465}
466
467/// A response to getting several threads channels.
468///
469/// Discord docs: defined [multiple times](https://discord.com/developers/docs/topics/threads#enumerating-threads):
470/// [1](https://discord.com/developers/docs/resources/guild#list-active-guild-threads-response-body),
471/// [2](https://discord.com/developers/docs/resources/channel#list-private-archived-threads-response-body),
472/// [3](https://discord.com/developers/docs/resources/channel#list-public-archived-threads-response-body),
473/// [4](https://discord.com/developers/docs/resources/channel#list-private-archived-threads-response-body)
474#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
475#[derive(Clone, Debug, Deserialize, Serialize)]
476#[non_exhaustive]
477pub struct ThreadsData {
478    /// The threads channels.
479    pub threads: Vec<GuildChannel>,
480    /// A thread member for each returned thread the current user has joined.
481    pub members: Vec<ThreadMember>,
482    /// Whether there are potentially more threads that could be returned on a subsequent call.
483    #[serde(default)]
484    pub has_more: bool,
485}
486
487/// An object that specifies the emoji to use for Forum related emoji parameters.
488///
489/// See [Discord](https://discord.com/developers/docs/resources/channel#default-reaction-object)
490/// [docs]()
491#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
492#[derive(Debug, Clone)]
493#[non_exhaustive]
494pub enum ForumEmoji {
495    /// The id of a guild's custom emoji.
496    Id(EmojiId),
497    /// The unicode character of the emoji.
498    Name(String),
499}
500
501#[derive(Deserialize)]
502struct RawForumEmoji {
503    emoji_id: Option<EmojiId>,
504    emoji_name: Option<String>,
505}
506
507impl serde::Serialize for ForumEmoji {
508    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
509        let mut map = serializer.serialize_map(Some(2))?;
510        match self {
511            Self::Id(id) => {
512                map.serialize_entry("emoji_id", id)?;
513                map.serialize_entry("emoji_name", &None::<()>)?;
514            },
515            Self::Name(name) => {
516                map.serialize_entry("emoji_id", &None::<()>)?;
517                map.serialize_entry("emoji_name", name)?;
518            },
519        };
520
521        map.end()
522    }
523}
524
525impl<'de> serde::Deserialize<'de> for ForumEmoji {
526    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
527        let helper = RawForumEmoji::deserialize(deserializer)?;
528        match (helper.emoji_id, helper.emoji_name) {
529            (Some(id), None) => Ok(ForumEmoji::Id(id)),
530            (None, Some(name)) => Ok(ForumEmoji::Name(name)),
531            (None, None) => {
532                Err(serde::de::Error::custom("expected emoji_name or emoji_id, found neither"))
533            },
534            (Some(_), Some(_)) => {
535                Err(serde::de::Error::custom("expected emoji_name or emoji_id, found both"))
536            },
537        }
538    }
539}
540
541/// An object that represents a tag able to be applied to a thread in a `GUILD_FORUM` channel.
542///
543/// See [Discord docs](https://discord.com/developers/docs/resources/channel#forum-tag-object)
544#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
545#[derive(Debug, Clone, Serialize, Deserialize)]
546#[non_exhaustive]
547pub struct ForumTag {
548    /// The id of the tag.
549    pub id: ForumTagId,
550    /// The name of the tag (0-20 characters).
551    pub name: String,
552    /// Whether this tag can only be added to or removed from threads by a member with the
553    /// MANAGE_THREADS permission.
554    pub moderated: bool,
555    /// The emoji to display next to the tag.
556    #[serde(flatten)]
557    pub emoji: Option<ForumEmoji>,
558}
559
560enum_number! {
561    /// The sort order for threads in a forum.
562    ///
563    /// [Discord docs](https://discord.com/developers/docs/resources/channel#channel-object-sort-order-types).
564    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
565    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
566    #[serde(from = "u8", into = "u8")]
567    #[non_exhaustive]
568    pub enum SortOrder {
569        /// Sort forum posts by activity.
570        LatestActivity = 0,
571        /// Sort forum posts by creation time (from most recent to oldest).
572        CreationDate = 1,
573        _ => Unknown(u8),
574    }
575}
576
577bitflags! {
578    /// Describes extra features of the channel.
579    ///
580    /// [Discord docs](https://discord.com/developers/docs/resources/channel#channel-object-channel-flags).
581    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
582    #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
583    pub struct ChannelFlags: u64 {
584        /// This thread is pinned to the top of its parent GUILD_FORUM channel
585        const PINNED = 1 << 1;
586        /// Whether a tag is required to be specified when creating a
587        /// thread in a GUILD_FORUM channel. Tags are specified in the applied_tags field.
588        const REQUIRE_TAG = 1 << 4;
589    }
590}