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