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