Skip to main content

twilight_model/application/interaction/
resolved.rs

1use crate::user::AvatarDecorationData;
2use crate::{
3    channel::{Attachment, ChannelType, Message, thread::ThreadMetadata},
4    guild::{MemberFlags, Permissions, Role},
5    id::{
6        Id,
7        marker::{AttachmentMarker, ChannelMarker, MessageMarker, RoleMarker, UserMarker},
8    },
9    user::User,
10    util::{ImageHash, Timestamp},
11};
12use serde::{Deserialize, Serialize};
13use std::collections::hash_map::HashMap;
14
15/// Resolved mentioned resources.
16///
17/// See [Discord Docs/Resolved Data Structure].
18///
19/// [`ApplicationCommand`]: crate::application::interaction::InteractionType::ApplicationCommand
20/// [Discord Docs/Resolved Data Structure]: https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure
21#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
22pub struct InteractionDataResolved {
23    /// Map of resolved attachments.
24    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
25    pub attachments: HashMap<Id<AttachmentMarker>, Attachment>,
26    /// Map of resolved channels.
27    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
28    pub channels: HashMap<Id<ChannelMarker>, InteractionChannel>,
29    /// Map of resolved members.
30    ///
31    /// Resolved members' ID will map to a resolved user as well.
32    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
33    pub members: HashMap<Id<UserMarker>, InteractionMember>,
34    /// Map of resolved messages.
35    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
36    pub messages: HashMap<Id<MessageMarker>, Message>,
37    /// Map of resolved roles.
38    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
39    pub roles: HashMap<Id<RoleMarker>, Role>,
40    /// Map of resolved users.
41    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
42    pub users: HashMap<Id<UserMarker>, User>,
43}
44
45/// Partial channel resolved from an [`Interaction`].
46///
47/// [`Interaction`]: crate::application::interaction::Interaction
48#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
49pub struct InteractionChannel {
50    /// ID of the channel.
51    pub id: Id<ChannelMarker>,
52    /// Type of the channel.
53    ///
54    /// This can be used to determine what fields *might* be available.
55    #[serde(rename = "type")]
56    pub kind: ChannelType,
57    /// Name of the channel.
58    pub name: String,
59    /// ID of the channel the thread was created in.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub parent_id: Option<Id<ChannelMarker>>,
62    /// Computed permissions, including overwrites, for the invoking user in the channel.
63    pub permissions: Permissions,
64    /// Computed permissions, including overwrites, for the bot in the channel.
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub app_permissions: Option<Permissions>,
67    /// Metadata about a thread.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub thread_metadata: Option<ThreadMetadata>,
70}
71
72/// Partial member resolved from an [`Interaction`].
73///
74/// [`Interaction`]: crate::application::interaction::Interaction
75#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
76pub struct InteractionMember {
77    /// Member's guild avatar.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub avatar: Option<ImageHash>,
80    /// The member's guild avatar decoration.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub avatar_decoration_data: Option<AvatarDecorationData>,
83    /// Member's guild banner.
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub banner: Option<ImageHash>,
86    /// If the member is timed out, when the timeout will expire.
87    pub communication_disabled_until: Option<Timestamp>,
88    /// Flags for the member.
89    ///
90    /// Defaults to an empty bitfield.
91    pub flags: MemberFlags,
92    /// Member guild join date.
93    pub joined_at: Option<Timestamp>,
94    /// Member nickname.
95    pub nick: Option<String>,
96    /// Whether the user has yet to pass the guild's Membership Screening
97    /// requirements.
98    pub pending: bool,
99    /// Total permissions of the member in this channel including overwrites
100    pub permissions: Permissions,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub premium_since: Option<Timestamp>,
103    /// Member roles.
104    #[serde(default)]
105    pub roles: Vec<Id<RoleMarker>>,
106}
107
108#[cfg(test)]
109mod tests {
110    use super::{InteractionChannel, InteractionDataResolved, InteractionMember};
111    use crate::guild::RoleColors;
112    use crate::user::PrimaryGuild;
113    use crate::{
114        channel::{
115            Attachment, ChannelType, Message,
116            message::{
117                MessageFlags, MessageType,
118                sticker::{MessageSticker, StickerFormatType},
119            },
120        },
121        guild::{MemberFlags, PartialMember, Permissions, Role, RoleFlags},
122        id::Id,
123        test::image_hash,
124        user::{PremiumType, User, UserFlags},
125        util::datetime::{Timestamp, TimestampParseError},
126    };
127    use serde_test::Token;
128    use std::str::FromStr;
129
130    #[test]
131    #[allow(clippy::too_many_lines, deprecated)]
132    fn test_data_resolved() -> Result<(), TimestampParseError> {
133        let joined_at = Some(Timestamp::from_str("2021-08-10T12:18:37.000000+00:00")?);
134        let timestamp = Timestamp::from_str("2020-02-02T02:02:02.020000+00:00")?;
135        let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
136
137        let value = InteractionDataResolved {
138            attachments: IntoIterator::into_iter([(
139                Id::new(400),
140                Attachment {
141                    content_type: Some("image/png".to_owned()),
142                    ephemeral: true,
143                    filename: "rainbow_dash.png".to_owned(),
144                    flags: None,
145                    description: None,
146                    duration_secs: None,
147                    height: Some(2674),
148                    id: Id::new(400),
149                    // Thumbhash of the twilight project logo.
150                    placeholder: Some("zSeKDQIoCLiHeIKeiLyfrsBqCIaYaHJ2Vg".to_owned()),
151                    placeholder_version: Some(1),
152                    proxy_url: "https://proxy.example.com/rainbow_dash.png".to_owned(),
153                    size: 13370,
154                    title: None,
155                    url: "https://example.com/rainbow_dash.png".to_owned(),
156                    waveform: None,
157                    width: Some(1337),
158                },
159            )])
160            .collect(),
161            channels: IntoIterator::into_iter([(
162                Id::new(100),
163                InteractionChannel {
164                    id: Id::new(100),
165                    kind: ChannelType::GuildText,
166                    name: "channel name".into(),
167                    parent_id: None,
168                    permissions: Permissions::empty(),
169                    app_permissions: None,
170                    thread_metadata: None,
171                },
172            )])
173            .collect(),
174            members: IntoIterator::into_iter([(
175                Id::new(300),
176                InteractionMember {
177                    avatar: None,
178                    avatar_decoration_data: None,
179                    banner: None,
180                    communication_disabled_until: None,
181                    flags,
182                    joined_at,
183                    nick: None,
184                    pending: false,
185                    permissions: Permissions::empty(),
186                    premium_since: None,
187                    roles: Vec::new(),
188                },
189            )])
190            .collect(),
191            messages: IntoIterator::into_iter([(
192                Id::new(4),
193                Message {
194                    activity: None,
195                    application: None,
196                    application_id: None,
197                    attachments: Vec::new(),
198                    author: User {
199                        accent_color: None,
200                        avatar: Some(image_hash::AVATAR),
201                        avatar_decoration: None,
202                        avatar_decoration_data: None,
203                        banner: None,
204                        bot: false,
205                        discriminator: 1,
206                        email: None,
207                        flags: None,
208                        global_name: Some("test".to_owned()),
209                        id: Id::new(3),
210                        locale: None,
211                        mfa_enabled: None,
212                        name: "test".to_owned(),
213                        premium_type: None,
214                        primary_guild: None,
215                        public_flags: None,
216                        system: None,
217                        verified: None,
218                    },
219                    call: None,
220                    channel_id: Id::new(2),
221                    components: Vec::new(),
222                    content: "ping".to_owned(),
223                    edited_timestamp: None,
224                    embeds: Vec::new(),
225                    flags: Some(MessageFlags::empty()),
226                    guild_id: Some(Id::new(1)),
227                    id: Id::new(4),
228                    interaction: None,
229                    interaction_metadata: None,
230                    kind: MessageType::Regular,
231                    member: Some(PartialMember {
232                        avatar: None,
233                        avatar_decoration_data: None,
234                        banner: None,
235                        communication_disabled_until: None,
236                        flags,
237                        deaf: false,
238                        joined_at,
239                        mute: false,
240                        nick: Some("member nick".to_owned()),
241                        permissions: None,
242                        premium_since: None,
243                        roles: Vec::new(),
244                        user: None,
245                    }),
246                    mention_channels: Vec::new(),
247                    mention_everyone: false,
248                    mention_roles: Vec::new(),
249                    mentions: Vec::new(),
250                    message_snapshots: Vec::new(),
251                    pinned: false,
252                    poll: None,
253                    reactions: Vec::new(),
254                    reference: None,
255                    referenced_message: None,
256                    role_subscription_data: None,
257                    sticker_items: vec![MessageSticker {
258                        format_type: StickerFormatType::Png,
259                        id: Id::new(1),
260                        name: "sticker name".to_owned(),
261                    }],
262                    timestamp,
263                    thread: None,
264                    tts: false,
265                    webhook_id: None,
266                },
267            )])
268            .collect(),
269            roles: IntoIterator::into_iter([(
270                Id::new(400),
271                Role {
272                    color: 0,
273                    colors: RoleColors {
274                        primary_color: 0,
275                        secondary_color: None,
276                        tertiary_color: None,
277                    },
278                    hoist: true,
279                    icon: None,
280                    id: Id::new(400),
281                    managed: false,
282                    mentionable: true,
283                    name: "test".to_owned(),
284                    permissions: Permissions::ADMINISTRATOR,
285                    position: 12,
286                    flags: RoleFlags::empty(),
287                    tags: None,
288                    unicode_emoji: None,
289                },
290            )])
291            .collect(),
292            users: IntoIterator::into_iter([(
293                Id::new(300),
294                User {
295                    accent_color: None,
296                    avatar: Some(image_hash::AVATAR),
297                    avatar_decoration: None,
298                    avatar_decoration_data: None,
299                    banner: None,
300                    bot: false,
301                    discriminator: 1,
302                    email: Some("address@example.com".to_owned()),
303                    flags: Some(UserFlags::PREMIUM_EARLY_SUPPORTER | UserFlags::VERIFIED_DEVELOPER),
304                    global_name: Some("test".to_owned()),
305                    id: Id::new(300),
306                    locale: Some("en-us".to_owned()),
307                    mfa_enabled: Some(true),
308                    name: "test".to_owned(),
309                    premium_type: Some(PremiumType::Nitro),
310                    primary_guild: Some(PrimaryGuild {
311                        identity_guild_id: Some(Id::new(169_256_939_211_980_800)),
312                        identity_enabled: Some(true),
313                        tag: Some("DISC".to_owned()),
314                        badge: Some("1269e74af4df7417b13759eae50c83dc".parse().unwrap()),
315                    }),
316                    public_flags: Some(
317                        UserFlags::PREMIUM_EARLY_SUPPORTER | UserFlags::VERIFIED_DEVELOPER,
318                    ),
319                    system: None,
320                    verified: Some(true),
321                },
322            )])
323            .collect(),
324        };
325
326        serde_test::assert_tokens(
327            &value,
328            &[
329                Token::Struct {
330                    name: "InteractionDataResolved",
331                    len: 6,
332                },
333                Token::Str("attachments"),
334                Token::Map { len: Some(1) },
335                Token::NewtypeStruct { name: "Id" },
336                Token::Str("400"),
337                Token::Struct {
338                    name: "Attachment",
339                    len: 11,
340                },
341                Token::Str("content_type"),
342                Token::Some,
343                Token::Str("image/png"),
344                Token::Str("ephemeral"),
345                Token::Bool(true),
346                Token::Str("filename"),
347                Token::Str("rainbow_dash.png"),
348                Token::Str("height"),
349                Token::Some,
350                Token::U64(2674),
351                Token::Str("id"),
352                Token::NewtypeStruct { name: "Id" },
353                Token::Str("400"),
354                Token::Str("placeholder"),
355                Token::Some,
356                Token::Str("zSeKDQIoCLiHeIKeiLyfrsBqCIaYaHJ2Vg"),
357                Token::Str("placeholder_version"),
358                Token::Some,
359                Token::U64(1),
360                Token::Str("proxy_url"),
361                Token::Str("https://proxy.example.com/rainbow_dash.png"),
362                Token::Str("size"),
363                Token::U64(13370),
364                Token::Str("url"),
365                Token::Str("https://example.com/rainbow_dash.png"),
366                Token::Str("width"),
367                Token::Some,
368                Token::U64(1337),
369                Token::StructEnd,
370                Token::MapEnd,
371                Token::Str("channels"),
372                Token::Map { len: Some(1) },
373                Token::NewtypeStruct { name: "Id" },
374                Token::Str("100"),
375                Token::Struct {
376                    name: "InteractionChannel",
377                    len: 4,
378                },
379                Token::Str("id"),
380                Token::NewtypeStruct { name: "Id" },
381                Token::Str("100"),
382                Token::Str("type"),
383                Token::U8(0),
384                Token::Str("name"),
385                Token::Str("channel name"),
386                Token::Str("permissions"),
387                Token::Str("0"),
388                Token::StructEnd,
389                Token::MapEnd,
390                Token::Str("members"),
391                Token::Map { len: Some(1) },
392                Token::NewtypeStruct { name: "Id" },
393                Token::Str("300"),
394                Token::Struct {
395                    name: "InteractionMember",
396                    len: 7,
397                },
398                Token::Str("communication_disabled_until"),
399                Token::None,
400                Token::Str("flags"),
401                Token::U64(flags.bits()),
402                Token::Str("joined_at"),
403                Token::Some,
404                Token::Str("2021-08-10T12:18:37.000000+00:00"),
405                Token::Str("nick"),
406                Token::None,
407                Token::Str("pending"),
408                Token::Bool(false),
409                Token::Str("permissions"),
410                Token::Str("0"),
411                Token::Str("roles"),
412                Token::Seq { len: Some(0) },
413                Token::SeqEnd,
414                Token::StructEnd,
415                Token::MapEnd,
416                Token::Str("messages"),
417                Token::Map { len: Some(1) },
418                Token::NewtypeStruct { name: "Id" },
419                Token::Str("4"),
420                Token::Struct {
421                    name: "Message",
422                    len: 19,
423                },
424                Token::Str("attachments"),
425                Token::Seq { len: Some(0) },
426                Token::SeqEnd,
427                Token::Str("author"),
428                Token::Struct {
429                    name: "User",
430                    len: 10,
431                },
432                Token::Str("accent_color"),
433                Token::None,
434                Token::Str("avatar"),
435                Token::Some,
436                Token::Str(image_hash::AVATAR_INPUT),
437                Token::Str("avatar_decoration"),
438                Token::None,
439                Token::Str("avatar_decoration_data"),
440                Token::None,
441                Token::Str("banner"),
442                Token::None,
443                Token::Str("bot"),
444                Token::Bool(false),
445                Token::Str("discriminator"),
446                Token::Str("0001"),
447                Token::Str("global_name"),
448                Token::Some,
449                Token::Str("test"),
450                Token::Str("id"),
451                Token::NewtypeStruct { name: "Id" },
452                Token::Str("3"),
453                Token::Str("username"),
454                Token::Str("test"),
455                Token::StructEnd,
456                Token::Str("call"),
457                Token::None,
458                Token::Str("channel_id"),
459                Token::NewtypeStruct { name: "Id" },
460                Token::Str("2"),
461                Token::Str("content"),
462                Token::Str("ping"),
463                Token::Str("edited_timestamp"),
464                Token::None,
465                Token::Str("embeds"),
466                Token::Seq { len: Some(0) },
467                Token::SeqEnd,
468                Token::Str("flags"),
469                Token::Some,
470                Token::U64(0),
471                Token::Str("guild_id"),
472                Token::Some,
473                Token::NewtypeStruct { name: "Id" },
474                Token::Str("1"),
475                Token::Str("id"),
476                Token::NewtypeStruct { name: "Id" },
477                Token::Str("4"),
478                Token::Str("type"),
479                Token::U8(0),
480                Token::Str("member"),
481                Token::Some,
482                Token::Struct {
483                    name: "PartialMember",
484                    len: 8,
485                },
486                Token::Str("communication_disabled_until"),
487                Token::None,
488                Token::Str("deaf"),
489                Token::Bool(false),
490                Token::Str("flags"),
491                Token::U64(flags.bits()),
492                Token::Str("joined_at"),
493                Token::Some,
494                Token::Str("2021-08-10T12:18:37.000000+00:00"),
495                Token::Str("mute"),
496                Token::Bool(false),
497                Token::Str("nick"),
498                Token::Some,
499                Token::Str("member nick"),
500                Token::Str("roles"),
501                Token::Seq { len: Some(0) },
502                Token::SeqEnd,
503                Token::Str("user"),
504                Token::None,
505                Token::StructEnd,
506                Token::Str("mention_everyone"),
507                Token::Bool(false),
508                Token::Str("mention_roles"),
509                Token::Seq { len: Some(0) },
510                Token::SeqEnd,
511                Token::Str("mentions"),
512                Token::Seq { len: Some(0) },
513                Token::SeqEnd,
514                Token::Str("pinned"),
515                Token::Bool(false),
516                Token::Str("sticker_items"),
517                Token::Seq { len: Some(1) },
518                Token::Struct {
519                    name: "MessageSticker",
520                    len: 3,
521                },
522                Token::Str("format_type"),
523                Token::U8(1),
524                Token::Str("id"),
525                Token::NewtypeStruct { name: "Id" },
526                Token::Str("1"),
527                Token::Str("name"),
528                Token::Str("sticker name"),
529                Token::StructEnd,
530                Token::SeqEnd,
531                Token::Str("timestamp"),
532                Token::Str("2020-02-02T02:02:02.020000+00:00"),
533                Token::Str("tts"),
534                Token::Bool(false),
535                Token::StructEnd,
536                Token::MapEnd,
537                Token::Str("roles"),
538                Token::Map { len: Some(1) },
539                Token::NewtypeStruct { name: "Id" },
540                Token::Str("400"),
541                Token::Struct {
542                    name: "Role",
543                    len: 10,
544                },
545                Token::Str("color"),
546                Token::U32(0),
547                Token::Str("colors"),
548                Token::Struct {
549                    name: "RoleColors",
550                    len: 3,
551                },
552                Token::Str("primary_color"),
553                Token::U32(0),
554                Token::Str("secondary_color"),
555                Token::None,
556                Token::Str("tertiary_color"),
557                Token::None,
558                Token::StructEnd,
559                Token::Str("hoist"),
560                Token::Bool(true),
561                Token::Str("id"),
562                Token::NewtypeStruct { name: "Id" },
563                Token::Str("400"),
564                Token::Str("managed"),
565                Token::Bool(false),
566                Token::Str("mentionable"),
567                Token::Bool(true),
568                Token::Str("name"),
569                Token::Str("test"),
570                Token::Str("permissions"),
571                Token::Str("8"),
572                Token::Str("position"),
573                Token::I64(12),
574                Token::Str("flags"),
575                Token::U64(0),
576                Token::StructEnd,
577                Token::MapEnd,
578                Token::Str("users"),
579                Token::Map { len: Some(1) },
580                Token::NewtypeStruct { name: "Id" },
581                Token::Str("300"),
582                Token::Struct {
583                    name: "User",
584                    len: 18,
585                },
586                Token::Str("accent_color"),
587                Token::None,
588                Token::Str("avatar"),
589                Token::Some,
590                Token::Str(image_hash::AVATAR_INPUT),
591                Token::Str("avatar_decoration"),
592                Token::None,
593                Token::Str("avatar_decoration_data"),
594                Token::None,
595                Token::Str("banner"),
596                Token::None,
597                Token::Str("bot"),
598                Token::Bool(false),
599                Token::Str("discriminator"),
600                Token::Str("0001"),
601                Token::Str("email"),
602                Token::Some,
603                Token::Str("address@example.com"),
604                Token::Str("flags"),
605                Token::Some,
606                Token::U64(131_584),
607                Token::Str("global_name"),
608                Token::Some,
609                Token::Str("test"),
610                Token::Str("id"),
611                Token::NewtypeStruct { name: "Id" },
612                Token::Str("300"),
613                Token::Str("locale"),
614                Token::Some,
615                Token::Str("en-us"),
616                Token::Str("mfa_enabled"),
617                Token::Some,
618                Token::Bool(true),
619                Token::Str("username"),
620                Token::Str("test"),
621                Token::Str("premium_type"),
622                Token::Some,
623                Token::U8(2),
624                Token::Str("primary_guild"),
625                Token::Some,
626                Token::Struct {
627                    name: "PrimaryGuild",
628                    len: 4,
629                },
630                Token::Str("identity_guild_id"),
631                Token::Some,
632                Token::NewtypeStruct { name: "Id" },
633                Token::Str("169256939211980800"),
634                Token::Str("identity_enabled"),
635                Token::Some,
636                Token::Bool(true),
637                Token::Str("tag"),
638                Token::Some,
639                Token::Str("DISC"),
640                Token::Str("badge"),
641                Token::Some,
642                Token::Str("1269e74af4df7417b13759eae50c83dc"),
643                Token::StructEnd,
644                Token::Str("public_flags"),
645                Token::Some,
646                Token::U64(131_584),
647                Token::Str("verified"),
648                Token::Some,
649                Token::Bool(true),
650                Token::StructEnd,
651                Token::MapEnd,
652                Token::StructEnd,
653            ],
654        );
655
656        Ok(())
657    }
658}