twilight_model/application/interaction/
resolved.rs

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