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                    proxy_url: "https://proxy.example.com/rainbow_dash.png".to_owned(),
150                    size: 13370,
151                    title: None,
152                    url: "https://example.com/rainbow_dash.png".to_owned(),
153                    waveform: None,
154                    width: Some(1337),
155                },
156            )])
157            .collect(),
158            channels: IntoIterator::into_iter([(
159                Id::new(100),
160                InteractionChannel {
161                    id: Id::new(100),
162                    kind: ChannelType::GuildText,
163                    name: "channel name".into(),
164                    parent_id: None,
165                    permissions: Permissions::empty(),
166                    app_permissions: None,
167                    thread_metadata: None,
168                },
169            )])
170            .collect(),
171            members: IntoIterator::into_iter([(
172                Id::new(300),
173                InteractionMember {
174                    avatar: None,
175                    avatar_decoration_data: None,
176                    banner: None,
177                    communication_disabled_until: None,
178                    flags,
179                    joined_at,
180                    nick: None,
181                    pending: false,
182                    permissions: Permissions::empty(),
183                    premium_since: None,
184                    roles: Vec::new(),
185                },
186            )])
187            .collect(),
188            messages: IntoIterator::into_iter([(
189                Id::new(4),
190                Message {
191                    activity: None,
192                    application: None,
193                    application_id: None,
194                    attachments: Vec::new(),
195                    author: User {
196                        accent_color: None,
197                        avatar: Some(image_hash::AVATAR),
198                        avatar_decoration: None,
199                        avatar_decoration_data: None,
200                        banner: None,
201                        bot: false,
202                        discriminator: 1,
203                        email: None,
204                        flags: None,
205                        global_name: Some("test".to_owned()),
206                        id: Id::new(3),
207                        locale: None,
208                        mfa_enabled: None,
209                        name: "test".to_owned(),
210                        premium_type: None,
211                        primary_guild: None,
212                        public_flags: None,
213                        system: None,
214                        verified: None,
215                    },
216                    call: None,
217                    channel_id: Id::new(2),
218                    components: Vec::new(),
219                    content: "ping".to_owned(),
220                    edited_timestamp: None,
221                    embeds: Vec::new(),
222                    flags: Some(MessageFlags::empty()),
223                    guild_id: Some(Id::new(1)),
224                    id: Id::new(4),
225                    interaction: None,
226                    interaction_metadata: None,
227                    kind: MessageType::Regular,
228                    member: Some(PartialMember {
229                        avatar: None,
230                        avatar_decoration_data: None,
231                        banner: None,
232                        communication_disabled_until: None,
233                        flags,
234                        deaf: false,
235                        joined_at,
236                        mute: false,
237                        nick: Some("member nick".to_owned()),
238                        permissions: None,
239                        premium_since: None,
240                        roles: Vec::new(),
241                        user: None,
242                    }),
243                    mention_channels: Vec::new(),
244                    mention_everyone: false,
245                    mention_roles: Vec::new(),
246                    mentions: Vec::new(),
247                    message_snapshots: Vec::new(),
248                    pinned: false,
249                    poll: None,
250                    reactions: Vec::new(),
251                    reference: None,
252                    referenced_message: None,
253                    role_subscription_data: None,
254                    sticker_items: vec![MessageSticker {
255                        format_type: StickerFormatType::Png,
256                        id: Id::new(1),
257                        name: "sticker name".to_owned(),
258                    }],
259                    timestamp,
260                    thread: None,
261                    tts: false,
262                    webhook_id: None,
263                },
264            )])
265            .collect(),
266            roles: IntoIterator::into_iter([(
267                Id::new(400),
268                Role {
269                    color: 0,
270                    colors: RoleColors {
271                        primary_color: 0,
272                        secondary_color: None,
273                        tertiary_color: None,
274                    },
275                    hoist: true,
276                    icon: None,
277                    id: Id::new(400),
278                    managed: false,
279                    mentionable: true,
280                    name: "test".to_owned(),
281                    permissions: Permissions::ADMINISTRATOR,
282                    position: 12,
283                    flags: RoleFlags::empty(),
284                    tags: None,
285                    unicode_emoji: None,
286                },
287            )])
288            .collect(),
289            users: IntoIterator::into_iter([(
290                Id::new(300),
291                User {
292                    accent_color: None,
293                    avatar: Some(image_hash::AVATAR),
294                    avatar_decoration: None,
295                    avatar_decoration_data: None,
296                    banner: None,
297                    bot: false,
298                    discriminator: 1,
299                    email: Some("address@example.com".to_owned()),
300                    flags: Some(UserFlags::PREMIUM_EARLY_SUPPORTER | UserFlags::VERIFIED_DEVELOPER),
301                    global_name: Some("test".to_owned()),
302                    id: Id::new(300),
303                    locale: Some("en-us".to_owned()),
304                    mfa_enabled: Some(true),
305                    name: "test".to_owned(),
306                    premium_type: Some(PremiumType::Nitro),
307                    primary_guild: Some(PrimaryGuild {
308                        identity_guild_id: Some(Id::new(169_256_939_211_980_800)),
309                        identity_enabled: Some(true),
310                        tag: Some("DISC".to_owned()),
311                        badge: Some("1269e74af4df7417b13759eae50c83dc".parse().unwrap()),
312                    }),
313                    public_flags: Some(
314                        UserFlags::PREMIUM_EARLY_SUPPORTER | UserFlags::VERIFIED_DEVELOPER,
315                    ),
316                    system: None,
317                    verified: Some(true),
318                },
319            )])
320            .collect(),
321        };
322
323        serde_test::assert_tokens(
324            &value,
325            &[
326                Token::Struct {
327                    name: "InteractionDataResolved",
328                    len: 6,
329                },
330                Token::Str("attachments"),
331                Token::Map { len: Some(1) },
332                Token::NewtypeStruct { name: "Id" },
333                Token::Str("400"),
334                Token::Struct {
335                    name: "Attachment",
336                    len: 9,
337                },
338                Token::Str("content_type"),
339                Token::Some,
340                Token::Str("image/png"),
341                Token::Str("ephemeral"),
342                Token::Bool(true),
343                Token::Str("filename"),
344                Token::Str("rainbow_dash.png"),
345                Token::Str("height"),
346                Token::Some,
347                Token::U64(2674),
348                Token::Str("id"),
349                Token::NewtypeStruct { name: "Id" },
350                Token::Str("400"),
351                Token::Str("proxy_url"),
352                Token::Str("https://proxy.example.com/rainbow_dash.png"),
353                Token::Str("size"),
354                Token::U64(13370),
355                Token::Str("url"),
356                Token::Str("https://example.com/rainbow_dash.png"),
357                Token::Str("width"),
358                Token::Some,
359                Token::U64(1337),
360                Token::StructEnd,
361                Token::MapEnd,
362                Token::Str("channels"),
363                Token::Map { len: Some(1) },
364                Token::NewtypeStruct { name: "Id" },
365                Token::Str("100"),
366                Token::Struct {
367                    name: "InteractionChannel",
368                    len: 4,
369                },
370                Token::Str("id"),
371                Token::NewtypeStruct { name: "Id" },
372                Token::Str("100"),
373                Token::Str("type"),
374                Token::U8(0),
375                Token::Str("name"),
376                Token::Str("channel name"),
377                Token::Str("permissions"),
378                Token::Str("0"),
379                Token::StructEnd,
380                Token::MapEnd,
381                Token::Str("members"),
382                Token::Map { len: Some(1) },
383                Token::NewtypeStruct { name: "Id" },
384                Token::Str("300"),
385                Token::Struct {
386                    name: "InteractionMember",
387                    len: 7,
388                },
389                Token::Str("communication_disabled_until"),
390                Token::None,
391                Token::Str("flags"),
392                Token::U64(flags.bits()),
393                Token::Str("joined_at"),
394                Token::Some,
395                Token::Str("2021-08-10T12:18:37.000000+00:00"),
396                Token::Str("nick"),
397                Token::None,
398                Token::Str("pending"),
399                Token::Bool(false),
400                Token::Str("permissions"),
401                Token::Str("0"),
402                Token::Str("roles"),
403                Token::Seq { len: Some(0) },
404                Token::SeqEnd,
405                Token::StructEnd,
406                Token::MapEnd,
407                Token::Str("messages"),
408                Token::Map { len: Some(1) },
409                Token::NewtypeStruct { name: "Id" },
410                Token::Str("4"),
411                Token::Struct {
412                    name: "Message",
413                    len: 19,
414                },
415                Token::Str("attachments"),
416                Token::Seq { len: Some(0) },
417                Token::SeqEnd,
418                Token::Str("author"),
419                Token::Struct {
420                    name: "User",
421                    len: 10,
422                },
423                Token::Str("accent_color"),
424                Token::None,
425                Token::Str("avatar"),
426                Token::Some,
427                Token::Str(image_hash::AVATAR_INPUT),
428                Token::Str("avatar_decoration"),
429                Token::None,
430                Token::Str("avatar_decoration_data"),
431                Token::None,
432                Token::Str("banner"),
433                Token::None,
434                Token::Str("bot"),
435                Token::Bool(false),
436                Token::Str("discriminator"),
437                Token::Str("0001"),
438                Token::Str("global_name"),
439                Token::Some,
440                Token::Str("test"),
441                Token::Str("id"),
442                Token::NewtypeStruct { name: "Id" },
443                Token::Str("3"),
444                Token::Str("username"),
445                Token::Str("test"),
446                Token::StructEnd,
447                Token::Str("call"),
448                Token::None,
449                Token::Str("channel_id"),
450                Token::NewtypeStruct { name: "Id" },
451                Token::Str("2"),
452                Token::Str("content"),
453                Token::Str("ping"),
454                Token::Str("edited_timestamp"),
455                Token::None,
456                Token::Str("embeds"),
457                Token::Seq { len: Some(0) },
458                Token::SeqEnd,
459                Token::Str("flags"),
460                Token::Some,
461                Token::U64(0),
462                Token::Str("guild_id"),
463                Token::Some,
464                Token::NewtypeStruct { name: "Id" },
465                Token::Str("1"),
466                Token::Str("id"),
467                Token::NewtypeStruct { name: "Id" },
468                Token::Str("4"),
469                Token::Str("type"),
470                Token::U8(0),
471                Token::Str("member"),
472                Token::Some,
473                Token::Struct {
474                    name: "PartialMember",
475                    len: 8,
476                },
477                Token::Str("communication_disabled_until"),
478                Token::None,
479                Token::Str("deaf"),
480                Token::Bool(false),
481                Token::Str("flags"),
482                Token::U64(flags.bits()),
483                Token::Str("joined_at"),
484                Token::Some,
485                Token::Str("2021-08-10T12:18:37.000000+00:00"),
486                Token::Str("mute"),
487                Token::Bool(false),
488                Token::Str("nick"),
489                Token::Some,
490                Token::Str("member nick"),
491                Token::Str("roles"),
492                Token::Seq { len: Some(0) },
493                Token::SeqEnd,
494                Token::Str("user"),
495                Token::None,
496                Token::StructEnd,
497                Token::Str("mention_everyone"),
498                Token::Bool(false),
499                Token::Str("mention_roles"),
500                Token::Seq { len: Some(0) },
501                Token::SeqEnd,
502                Token::Str("mentions"),
503                Token::Seq { len: Some(0) },
504                Token::SeqEnd,
505                Token::Str("pinned"),
506                Token::Bool(false),
507                Token::Str("sticker_items"),
508                Token::Seq { len: Some(1) },
509                Token::Struct {
510                    name: "MessageSticker",
511                    len: 3,
512                },
513                Token::Str("format_type"),
514                Token::U8(1),
515                Token::Str("id"),
516                Token::NewtypeStruct { name: "Id" },
517                Token::Str("1"),
518                Token::Str("name"),
519                Token::Str("sticker name"),
520                Token::StructEnd,
521                Token::SeqEnd,
522                Token::Str("timestamp"),
523                Token::Str("2020-02-02T02:02:02.020000+00:00"),
524                Token::Str("tts"),
525                Token::Bool(false),
526                Token::StructEnd,
527                Token::MapEnd,
528                Token::Str("roles"),
529                Token::Map { len: Some(1) },
530                Token::NewtypeStruct { name: "Id" },
531                Token::Str("400"),
532                Token::Struct {
533                    name: "Role",
534                    len: 10,
535                },
536                Token::Str("color"),
537                Token::U32(0),
538                Token::Str("colors"),
539                Token::Struct {
540                    name: "RoleColors",
541                    len: 3,
542                },
543                Token::Str("primary_color"),
544                Token::U32(0),
545                Token::Str("secondary_color"),
546                Token::None,
547                Token::Str("tertiary_color"),
548                Token::None,
549                Token::StructEnd,
550                Token::Str("hoist"),
551                Token::Bool(true),
552                Token::Str("id"),
553                Token::NewtypeStruct { name: "Id" },
554                Token::Str("400"),
555                Token::Str("managed"),
556                Token::Bool(false),
557                Token::Str("mentionable"),
558                Token::Bool(true),
559                Token::Str("name"),
560                Token::Str("test"),
561                Token::Str("permissions"),
562                Token::Str("8"),
563                Token::Str("position"),
564                Token::I64(12),
565                Token::Str("flags"),
566                Token::U64(0),
567                Token::StructEnd,
568                Token::MapEnd,
569                Token::Str("users"),
570                Token::Map { len: Some(1) },
571                Token::NewtypeStruct { name: "Id" },
572                Token::Str("300"),
573                Token::Struct {
574                    name: "User",
575                    len: 18,
576                },
577                Token::Str("accent_color"),
578                Token::None,
579                Token::Str("avatar"),
580                Token::Some,
581                Token::Str(image_hash::AVATAR_INPUT),
582                Token::Str("avatar_decoration"),
583                Token::None,
584                Token::Str("avatar_decoration_data"),
585                Token::None,
586                Token::Str("banner"),
587                Token::None,
588                Token::Str("bot"),
589                Token::Bool(false),
590                Token::Str("discriminator"),
591                Token::Str("0001"),
592                Token::Str("email"),
593                Token::Some,
594                Token::Str("address@example.com"),
595                Token::Str("flags"),
596                Token::Some,
597                Token::U64(131_584),
598                Token::Str("global_name"),
599                Token::Some,
600                Token::Str("test"),
601                Token::Str("id"),
602                Token::NewtypeStruct { name: "Id" },
603                Token::Str("300"),
604                Token::Str("locale"),
605                Token::Some,
606                Token::Str("en-us"),
607                Token::Str("mfa_enabled"),
608                Token::Some,
609                Token::Bool(true),
610                Token::Str("username"),
611                Token::Str("test"),
612                Token::Str("premium_type"),
613                Token::Some,
614                Token::U8(2),
615                Token::Str("primary_guild"),
616                Token::Some,
617                Token::Struct {
618                    name: "PrimaryGuild",
619                    len: 4,
620                },
621                Token::Str("identity_guild_id"),
622                Token::Some,
623                Token::NewtypeStruct { name: "Id" },
624                Token::Str("169256939211980800"),
625                Token::Str("identity_enabled"),
626                Token::Some,
627                Token::Bool(true),
628                Token::Str("tag"),
629                Token::Some,
630                Token::Str("DISC"),
631                Token::Str("badge"),
632                Token::Some,
633                Token::Str("1269e74af4df7417b13759eae50c83dc"),
634                Token::StructEnd,
635                Token::Str("public_flags"),
636                Token::Some,
637                Token::U64(131_584),
638                Token::Str("verified"),
639                Token::Some,
640                Token::Bool(true),
641                Token::StructEnd,
642                Token::MapEnd,
643                Token::StructEnd,
644            ],
645        );
646
647        Ok(())
648    }
649}