Skip to main content

twilight_model/channel/
mod.rs

1pub mod forum;
2pub mod message;
3pub mod permission_overwrite;
4pub mod stage_instance;
5pub mod thread;
6pub mod webhook;
7
8mod attachment;
9mod attachment_flags;
10mod channel_mention;
11mod channel_type;
12mod flags;
13mod followed_channel;
14mod video_quality_mode;
15
16pub use self::{
17    attachment::Attachment,
18    attachment_flags::AttachmentFlags,
19    channel_mention::ChannelMention,
20    channel_type::ChannelType,
21    flags::ChannelFlags,
22    followed_channel::FollowedChannel,
23    message::Message,
24    stage_instance::StageInstance,
25    video_quality_mode::VideoQualityMode,
26    webhook::{Webhook, WebhookType},
27};
28
29use crate::{
30    channel::{
31        forum::{DefaultReaction, ForumLayout, ForumSortOrder, ForumTag},
32        permission_overwrite::PermissionOverwrite,
33        thread::{AutoArchiveDuration, ThreadMember, ThreadMetadata},
34    },
35    id::{
36        Id,
37        marker::{
38            ApplicationMarker, ChannelMarker, GenericMarker, GuildMarker, TagMarker, UserMarker,
39        },
40    },
41    user::User,
42    util::{ImageHash, Timestamp},
43};
44use serde::{Deserialize, Serialize};
45
46/// Channel to send messages in, call with other users, organize groups, and
47/// more.
48///
49/// The `Channel` type is one overarching type for all types of channels: there
50/// is no distinction between audio channels, textual channels, guild channels,
51/// groups, threads, and so on. The type of channel can be determined by
52/// checking [`Channel::kind`], which can be used to determine what fields you
53/// might expect to be present.
54///
55/// For Discord's documentation on channels, refer to [Discord Docs/Channel].
56///
57/// # Obfuscated channels
58///
59/// Guild channels which a user can not access are referred to as "obfuscated
60/// channels". Obfuscated channels can be opted in to via the
61/// [`CHANNEL_OBFUSCATION`][crate::gateway::Capabilities::CHANNEL_OBFUSCATION]
62/// capability prior to its enforcement by Discord on November 16th, 2026; once
63/// released, obfuscated channels will be enforced for all users.
64///
65/// Via the HTTP API obfuscated channels aren't received.
66///
67/// Via the Gateway obfuscated channels are received with the
68/// [`ChannelFlags::CHANNEL_OBFUSCATED`] flag set in the [`flags`][Self::flags]
69/// field. Fields such as [`name`][Self::name] will be obfuscated with values
70/// such as `"___hidden___"`. The [`id`][Self::id], [`kind`][Self::kind],
71/// [`position`][Self::position], and [`parent_id`][Self::parent_id] fields
72/// won't be obfuscated and fields other than these should not be relied upon.
73/// Obfuscated channels will have a single
74/// [permission overwrite][permission_overwrite::PermissionOverwrite] denying
75/// the [`VIEW_CHANNEL`][`Permissions::VIEW_CHANNEL`] permission to the guild's
76/// `@everyone` role.
77///
78/// Refer to [Discord Docs/Channel Obfuscation].
79///
80/// [`Permissions::VIEW_CHANNEL`]: crate::guild::Permissions::VIEW_CHANNEL
81/// [Discord Docs/Channel]: https://discord.com/developers/docs/resources/channel
82/// [Discord Docs/Channel Obfuscation]: https://docs.discord.com/developers/resources/channel#channel-object-obfuscated-channels
83#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
84pub struct Channel {
85    /// ID of the application that created the channel.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub application_id: Option<Id<ApplicationMarker>>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub applied_tags: Option<Vec<Id<TagMarker>>>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub available_tags: Option<Vec<ForumTag>>,
92    /// Bitrate (in bits) setting of audio channels.
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub bitrate: Option<u32>,
95    /// Default duration without messages before the channel's threads
96    /// automatically archive.
97    ///
98    /// Automatic archive durations are not locked behind the guild's boost
99    /// level.
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub default_auto_archive_duration: Option<AutoArchiveDuration>,
102    /// Default forum layout view used to display posts in forum channels.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub default_forum_layout: Option<ForumLayout>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub default_reaction_emoji: Option<DefaultReaction>,
107    /// Default sort order used to display posts in forum channels.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub default_sort_order: Option<ForumSortOrder>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub default_thread_rate_limit_per_user: Option<u16>,
112    /// Flags of the channel.
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub flags: Option<ChannelFlags>,
115    /// ID of the guild the channel is in.
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub guild_id: Option<Id<GuildMarker>>,
118    /// Hash of the channel's icon.
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub icon: Option<ImageHash>,
121    /// ID of the channel.
122    pub id: Id<ChannelMarker>,
123    /// Whether users can be invited.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub invitable: Option<bool>,
126    /// Type of the channel.
127    ///
128    /// This can be used to determine what fields *might* be available.
129    #[serde(rename = "type")]
130    pub kind: ChannelType,
131    /// For text channels, this is the ID of the last message sent in the
132    /// channel.
133    ///
134    /// For forum channels, this is the ID of the last created thread in the
135    /// forum.
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub last_message_id: Option<Id<GenericMarker>>,
138    /// ID of the last message pinned in the channel.
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub last_pin_timestamp: Option<Timestamp>,
141    /// Whether the channel is managed by an application via the [`gdm.join`]
142    /// oauth scope.
143    ///
144    /// This is only applicable to [group channels].
145    ///
146    /// [`gdm.join`]: crate::oauth::scope::GDM_JOIN
147    /// [group channels]: ChannelType::Group
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub managed: Option<bool>,
150    /// Member that created the channel.
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub member: Option<ThreadMember>,
153    /// Number of members in the channel.
154    ///
155    /// At most a value of 50 is provided although the real number may be
156    /// higher.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub member_count: Option<i8>,
159    /// Number of messages in the channel.
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub message_count: Option<u32>,
162    /// Name of the channel.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub name: Option<String>,
165    /// Whether a thread was newly created.
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub newly_created: Option<bool>,
168    /// Whether the channel has been configured to be NSFW.
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub nsfw: Option<bool>,
171    /// ID of the creator of the channel.
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub owner_id: Option<Id<UserMarker>>,
174    /// ID of the parent channel.
175    ///
176    /// For guild channels this is the ID of the parent category channel.
177    ///
178    /// For threads this is the ID of the channel the thread was created in.
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub parent_id: Option<Id<ChannelMarker>>,
181    /// Explicit permission overwrites for members and roles.
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub permission_overwrites: Option<Vec<PermissionOverwrite>>,
184    /// Sorting position of the channel.
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub position: Option<i32>,
187    /// Amount of seconds a user has to wait before sending another message.
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub rate_limit_per_user: Option<u16>,
190    /// Recipients of the channel.
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub recipients: Option<Vec<User>>,
193    /// ID of the voice region for the channel.
194    ///
195    /// Defaults to automatic for applicable channels.
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub rtc_region: Option<String>,
198    /// Metadata about a thread.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub thread_metadata: Option<ThreadMetadata>,
201    /// Topic of the channel.
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub topic: Option<String>,
204    /// Number of messages ever sent in a thread.
205    ///
206    /// This is similar to [`message_count`][Self::message_count], but will not
207    /// decrement the number when a message is deleted.
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub total_message_sent: Option<u32>,
210    /// Number of users that may be in the channel.
211    ///
212    /// Zero refers to no limit.
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub user_limit: Option<u32>,
215    /// Camera video quality mode of the channel.
216    ///
217    /// Defaults to [`VideoQualityMode::Auto`] for applicable channels.
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub video_quality_mode: Option<VideoQualityMode>,
220}
221
222#[cfg(test)]
223mod tests {
224    use super::{AutoArchiveDuration, Channel, ChannelType, ThreadMember, ThreadMetadata};
225    use crate::{
226        channel::permission_overwrite::{PermissionOverwrite, PermissionOverwriteType},
227        guild::Permissions,
228        id::Id,
229        util::Timestamp,
230    };
231
232    // The deserializer for GuildChannel should skip over fields names that
233    // it couldn't deserialize.
234    #[test]
235    fn guild_channel_unknown_field_deserialization() {
236        let input = serde_json::json!({
237            "type": 0,
238            "topic": "a",
239            "rate_limit_per_user": 0,
240            "position": 0,
241            "permission_overwrites": [],
242            "parent_id": null,
243            "nsfw": false,
244            "name": "hey",
245            "last_message_id": "3",
246            "id": "2",
247            "guild_id": "1",
248            "guild_hashes": {
249                "version": 1,
250                "roles": {
251                    "hash": "aaaaaaaaaaa"
252                },
253                "metadata": {
254                    "hash": "bbbbbbbbbbb"
255                },
256                "channels": {
257                    "hash": "ccccccccccc"
258                }
259            },
260            "total_message_sent": 17,
261            "unknown_field": "the deserializer should skip unknown field names",
262        });
263
264        let value = Channel {
265            application_id: None,
266            applied_tags: None,
267            available_tags: None,
268            bitrate: None,
269            default_auto_archive_duration: None,
270            default_forum_layout: None,
271            default_reaction_emoji: None,
272            default_sort_order: None,
273            default_thread_rate_limit_per_user: None,
274            flags: None,
275            guild_id: Some(Id::new(1)),
276            icon: None,
277            id: Id::new(2),
278            invitable: None,
279            kind: ChannelType::GuildText,
280            last_message_id: Some(Id::new(3)),
281            last_pin_timestamp: None,
282            managed: None,
283            member: None,
284            member_count: None,
285            message_count: None,
286            name: Some("hey".to_owned()),
287            newly_created: None,
288            nsfw: Some(false),
289            owner_id: None,
290            parent_id: None,
291            permission_overwrites: Some(Vec::new()),
292            position: Some(0),
293            rate_limit_per_user: Some(0),
294            recipients: None,
295            rtc_region: None,
296            thread_metadata: None,
297            topic: Some("a".to_owned()),
298            total_message_sent: Some(17),
299            user_limit: None,
300            video_quality_mode: None,
301        };
302
303        assert_eq!(value, serde_json::from_value(input).unwrap());
304    }
305
306    #[test]
307    fn guild_category_channel_deserialization() {
308        let value = Channel {
309            application_id: None,
310            applied_tags: None,
311            available_tags: None,
312            bitrate: None,
313            default_auto_archive_duration: None,
314            default_forum_layout: None,
315            default_reaction_emoji: None,
316            default_sort_order: None,
317            default_thread_rate_limit_per_user: None,
318            flags: None,
319            guild_id: Some(Id::new(2)),
320            icon: None,
321            id: Id::new(1),
322            invitable: None,
323            kind: ChannelType::GuildCategory,
324            last_message_id: None,
325            last_pin_timestamp: None,
326            managed: None,
327            member: None,
328            member_count: None,
329            message_count: None,
330            name: Some("foo".to_owned()),
331            newly_created: None,
332            nsfw: None,
333            owner_id: None,
334            parent_id: None,
335            permission_overwrites: Some(Vec::new()),
336            position: Some(3),
337            rate_limit_per_user: None,
338            recipients: None,
339            rtc_region: None,
340            thread_metadata: None,
341            topic: None,
342            total_message_sent: Some(11),
343            user_limit: None,
344            video_quality_mode: None,
345        };
346        let permission_overwrites: Vec<PermissionOverwrite> = Vec::new();
347
348        assert_eq!(
349            value,
350            serde_json::from_value(serde_json::json!({
351                "id": "1",
352                "guild_id": Some("2"),
353                "name": "foo",
354                "permission_overwrites": permission_overwrites,
355                "position": 3,
356                "total_message_sent": 11,
357                "type": 4,
358            }))
359            .unwrap()
360        );
361    }
362
363    #[test]
364    fn guild_announcement_channel_deserialization() {
365        let value = Channel {
366            application_id: None,
367            applied_tags: None,
368            available_tags: None,
369            bitrate: None,
370            default_auto_archive_duration: None,
371            default_forum_layout: None,
372            default_reaction_emoji: None,
373            default_sort_order: None,
374            default_thread_rate_limit_per_user: None,
375            flags: None,
376            guild_id: Some(Id::new(2)),
377            icon: None,
378            id: Id::new(1),
379            invitable: None,
380            kind: ChannelType::GuildAnnouncement,
381            last_message_id: Some(Id::new(4)),
382            last_pin_timestamp: None,
383            managed: None,
384            member: None,
385            member_count: None,
386            message_count: None,
387            name: Some("news".to_owned()),
388            newly_created: None,
389            nsfw: Some(true),
390            owner_id: None,
391            parent_id: Some(Id::new(5)),
392            permission_overwrites: Some(Vec::new()),
393            position: Some(3),
394            rate_limit_per_user: None,
395            recipients: None,
396            rtc_region: None,
397            thread_metadata: None,
398            topic: Some("a news channel".to_owned()),
399            total_message_sent: None,
400            user_limit: None,
401            video_quality_mode: None,
402        };
403        let permission_overwrites: Vec<PermissionOverwrite> = Vec::new();
404
405        assert_eq!(
406            value,
407            serde_json::from_value(serde_json::json!({
408                "id": "1",
409                "guild_id": "2",
410                "name": "news",
411                "nsfw": true,
412                "last_message_id": "4",
413                "parent_id": "5",
414                "permission_overwrites": permission_overwrites,
415                "position": 3,
416                "topic": "a news channel",
417                "type": ChannelType::GuildAnnouncement,
418            }))
419            .unwrap()
420        );
421    }
422
423    #[test]
424    fn guild_announcement_thread_deserialization() {
425        let timestamp = Timestamp::from_secs(1_632_074_792).expect("non zero");
426        let formatted = timestamp.iso_8601().to_string();
427
428        let value = Channel {
429            application_id: None,
430            applied_tags: None,
431            available_tags: None,
432            bitrate: None,
433            default_auto_archive_duration: Some(AutoArchiveDuration::Hour),
434            default_forum_layout: None,
435            default_reaction_emoji: None,
436            default_sort_order: None,
437            default_thread_rate_limit_per_user: None,
438            flags: None,
439            guild_id: Some(Id::new(1)),
440            icon: None,
441            id: Id::new(6),
442            invitable: None,
443            kind: ChannelType::AnnouncementThread,
444            last_message_id: Some(Id::new(3)),
445            last_pin_timestamp: None,
446            managed: Some(true),
447            member: Some(ThreadMember {
448                flags: 0_u64,
449                id: Some(Id::new(4)),
450                join_timestamp: timestamp,
451                member: None,
452                presence: None,
453                user_id: Some(Id::new(5)),
454            }),
455            member_count: Some(50),
456            message_count: Some(50),
457            name: Some("newsthread".into()),
458            newly_created: Some(true),
459            nsfw: None,
460            owner_id: Some(Id::new(5)),
461            parent_id: Some(Id::new(2)),
462            permission_overwrites: None,
463            position: None,
464            rate_limit_per_user: Some(1000),
465            recipients: None,
466            rtc_region: None,
467            thread_metadata: Some(ThreadMetadata {
468                archived: false,
469                auto_archive_duration: AutoArchiveDuration::Day,
470                archive_timestamp: timestamp,
471                create_timestamp: Some(timestamp),
472                invitable: None,
473                locked: false,
474            }),
475            topic: None,
476            total_message_sent: None,
477            user_limit: None,
478            video_quality_mode: None,
479        };
480
481        assert_eq!(
482            value,
483            serde_json::from_value(serde_json::json!({
484                "id": "6",
485                "guild_id": "1",
486                "type": ChannelType::AnnouncementThread,
487                "last_message_id": "3",
488                "member": {
489                    "flags": 0,
490                    "id": "4",
491                    "join_timestamp": formatted,
492                    "user_id": "5",
493                },
494                "default_auto_archive_duration": 60,
495                "managed": true,
496                "member_count": 50,
497                "message_count": 50,
498                "name": "newsthread",
499                "newly_created": true,
500                "owner_id": "5",
501                "parent_id": "2",
502                "rate_limit_per_user": 1000,
503                "thread_metadata": {
504                    "archive_timestamp": formatted,
505                    "archived": false,
506                    "auto_archive_duration": AutoArchiveDuration::Day,
507                    "create_timestamp": formatted,
508                    "locked": false
509                }
510            }))
511            .unwrap()
512        )
513    }
514
515    #[test]
516    fn public_thread_deserialization() {
517        let timestamp = Timestamp::from_secs(1_632_074_792).expect("non zero");
518
519        let value = Channel {
520            application_id: None,
521            applied_tags: None,
522            available_tags: None,
523            bitrate: None,
524            default_auto_archive_duration: Some(AutoArchiveDuration::Hour),
525            default_forum_layout: None,
526            default_reaction_emoji: None,
527            default_sort_order: None,
528            default_thread_rate_limit_per_user: None,
529            flags: None,
530            guild_id: Some(Id::new(1)),
531            icon: None,
532            id: Id::new(6),
533            invitable: None,
534            kind: ChannelType::PublicThread,
535            last_message_id: Some(Id::new(3)),
536            last_pin_timestamp: None,
537            managed: Some(true),
538            member: Some(ThreadMember {
539                flags: 0_u64,
540                id: Some(Id::new(4)),
541                join_timestamp: timestamp,
542                member: None,
543                presence: None,
544                user_id: Some(Id::new(5)),
545            }),
546            member_count: Some(50),
547            message_count: Some(50),
548            name: Some("publicthread".into()),
549            newly_created: Some(true),
550            nsfw: None,
551            owner_id: Some(Id::new(5)),
552            parent_id: Some(Id::new(2)),
553            permission_overwrites: None,
554            position: None,
555            rate_limit_per_user: Some(1000),
556            recipients: None,
557            rtc_region: None,
558            thread_metadata: Some(ThreadMetadata {
559                archived: false,
560                auto_archive_duration: AutoArchiveDuration::Day,
561                archive_timestamp: timestamp,
562                create_timestamp: Some(timestamp),
563                invitable: None,
564                locked: false,
565            }),
566            topic: None,
567            total_message_sent: None,
568            user_limit: None,
569            video_quality_mode: None,
570        };
571
572        assert_eq!(
573            value,
574            serde_json::from_value(serde_json::json!({
575                "id": "6",
576                "guild_id": "1",
577                "type": ChannelType::PublicThread,
578                "last_message_id": "3",
579                "member": {
580                    "flags": 0,
581                    "id": "4",
582                    "join_timestamp": timestamp,
583                    "user_id": "5",
584                },
585                "default_auto_archive_duration": 60,
586                "managed": true,
587                "member_count": 50,
588                "message_count": 50,
589                "name": "publicthread",
590                "newly_created": true,
591                "owner_id": "5",
592                "parent_id": "2",
593                "rate_limit_per_user": 1000,
594                "thread_metadata": {
595                    "archive_timestamp": timestamp,
596                    "archived": false,
597                    "auto_archive_duration": AutoArchiveDuration::Day,
598                    "create_timestamp": timestamp,
599                    "locked": false
600                }
601            }))
602            .unwrap()
603        )
604    }
605
606    #[test]
607    fn private_thread_deserialization() {
608        let timestamp = Timestamp::from_secs(1_632_074_792).expect("non zero");
609        let formatted = timestamp.iso_8601().to_string();
610
611        let value = Channel {
612            application_id: None,
613            applied_tags: None,
614            available_tags: None,
615            bitrate: None,
616            default_auto_archive_duration: Some(AutoArchiveDuration::Hour),
617            default_forum_layout: None,
618            default_reaction_emoji: None,
619            default_sort_order: None,
620            default_thread_rate_limit_per_user: None,
621            flags: None,
622            guild_id: Some(Id::new(1)),
623            icon: None,
624            id: Id::new(6),
625            invitable: Some(true),
626            kind: ChannelType::PrivateThread,
627            last_message_id: Some(Id::new(3)),
628            last_pin_timestamp: None,
629            managed: Some(true),
630            member: Some(ThreadMember {
631                flags: 0_u64,
632                id: Some(Id::new(4)),
633                join_timestamp: timestamp,
634                member: None,
635                presence: None,
636                user_id: Some(Id::new(5)),
637            }),
638            // Old threads can have negative member counts, so we need to ensure
639            // we keep negative member counts around.
640            member_count: Some(-1),
641            message_count: Some(50),
642            name: Some("privatethread".into()),
643            newly_created: Some(true),
644            nsfw: None,
645            owner_id: Some(Id::new(5)),
646            parent_id: Some(Id::new(2)),
647            permission_overwrites: Some(Vec::from([PermissionOverwrite {
648                allow: Permissions::empty(),
649                deny: Permissions::empty(),
650                id: Id::new(5),
651                kind: PermissionOverwriteType::Member,
652            }])),
653            position: None,
654            rate_limit_per_user: Some(1000),
655            recipients: None,
656            rtc_region: None,
657            thread_metadata: Some(ThreadMetadata {
658                archived: false,
659                auto_archive_duration: AutoArchiveDuration::Day,
660                archive_timestamp: timestamp,
661                create_timestamp: Some(timestamp),
662                invitable: None,
663                locked: false,
664            }),
665            topic: None,
666            total_message_sent: None,
667            user_limit: None,
668            video_quality_mode: None,
669        };
670
671        assert_eq!(
672            value,
673            serde_json::from_value(serde_json::json!({
674                "id": "6",
675                "guild_id": "1",
676                "type": ChannelType::PrivateThread,
677                "last_message_id": "3",
678                "member": {
679                    "flags": 0,
680                    "id": "4",
681                    "join_timestamp": formatted,
682                    "user_id": "5",
683                },
684                "default_auto_archive_duration": 60,
685                "invitable": true,
686                "managed": true,
687                "member_count": -1,
688                "message_count": 50,
689                "name": "privatethread",
690                "newly_created": true,
691                "owner_id": "5",
692                "parent_id": "2",
693                "rate_limit_per_user": 1000,
694                "thread_metadata": {
695                    "archive_timestamp": formatted,
696                    "archived": false,
697                    "auto_archive_duration": AutoArchiveDuration::Day,
698                    "create_timestamp": formatted,
699                    "locked": false
700                },
701                "permission_overwrites": [
702                    {
703                        "allow": "0",
704                        "deny": "0",
705                        "type": 1,
706                        "id": "5"
707                    }
708                ]
709            }))
710            .unwrap()
711        )
712    }
713}