sparkle_impostor/
constructor.rs

1use twilight_http::Client;
2use twilight_model::channel::{
3    message::{MessageFlags, MessageType},
4    Message,
5};
6
7use crate::{
8    attachment_sticker, avatar, component, error::Error, later_messages, reaction, reference,
9    thread, MessageSource,
10};
11
12impl<'a> MessageSource<'a> {
13    /// Create [`MessageSource`] from a [`Message`]
14    ///
15    /// # Warnings
16    ///
17    /// `message.guild_id` is usually `None` even if the message is in a guild,
18    /// make sure this field is actually passed
19    ///
20    /// # Errors
21    ///
22    /// Returns [`Error::NotInGuild`] if the message is not in a guild,
23    ///
24    /// Returns [`Error::RichPresence`] if the message is related
25    /// to rich presence, which can't be recreated by bots
26    ///
27    /// Returns [`Error::Voice`] if the message is a voice message, which
28    /// bots currently can't create
29    ///
30    /// Returns [`Error::System`] if the message's type isn't
31    /// [`MessageType::Regular`] or [`MessageType::Reply`] or has role
32    /// subscription data, which are edge-cases that can't be replicated
33    /// correctly
34    ///
35    /// Returns [`Error::ContentInvalid`] if the message's content is
36    /// invalid, this may happen when the author has used Nitro perks to send a
37    /// message with over 2000 characters
38    pub fn from_message(message: &'a Message, http: &'a Client) -> Result<Self, Error> {
39        if message.activity.is_some() || message.application.is_some() {
40            return Err(Error::RichPresence);
41        }
42        if message
43            .flags
44            .is_some_and(|flags| flags.contains(MessageFlags::IS_VOICE_MESSAGE))
45        {
46            return Err(Error::Voice);
47        }
48        if !matches!(message.kind, MessageType::Regular | MessageType::Reply)
49            || message.role_subscription_data.is_some()
50        {
51            return Err(Error::System);
52        }
53        twilight_validate::message::content(&message.content).map_err(|_| Error::ContentInvalid)?;
54
55        let guild_id = message.guild_id.ok_or(Error::NotInGuild)?;
56
57        let url_components = component::filter_valid(&message.components);
58        let has_invalid_components = message.components != url_components;
59
60        let reference_info = message.referenced_message.as_ref().map_or_else(
61            || {
62                if message.kind == MessageType::Reply {
63                    reference::Info::UnknownOrDeleted
64                } else {
65                    reference::Info::None
66                }
67            },
68            |referenced_message| reference::Info::Reference(referenced_message),
69        );
70
71        let thread_info = message
72            .thread
73            .as_ref()
74            .map_or(thread::Info::Unknown, |thread| {
75                thread::Info::CreatedUnknown(Box::new(thread.clone()))
76            });
77
78        Ok(MessageSource {
79            source_id: message.id,
80            source_channel_id: message.channel_id,
81            source_thread_id: thread_info.id(),
82            content: message.content.clone(),
83            embeds: message.embeds.clone(),
84            tts: message.tts,
85            flags: message.flags,
86            channel_id: message.channel_id,
87            guild_id,
88            guild_emoji_ids: None,
89            username: message
90                .member
91                .as_ref()
92                .and_then(|member| member.nick.as_ref())
93                .unwrap_or(&message.author.name)
94                .clone(),
95            reference_info,
96            avatar_info: avatar::Info {
97                url: None,
98                user_id: message.author.id,
99                guild_id,
100                user_discriminator: message.author.discriminator,
101                user_avatar: message.author.avatar,
102                member_avatar: message.member.as_ref().and_then(|member| member.avatar),
103            },
104            webhook_name: "Message Cloner".to_owned(),
105            reaction_info: reaction::Info {
106                reactions: &message.reactions,
107            },
108            attachment_sticker_info: attachment_sticker::Info {
109                stickers: &message.sticker_items,
110                attachments: &message.attachments,
111                #[cfg(feature = "upload")]
112                attachments_upload: vec![],
113            },
114            component_info: component::Info {
115                url_components,
116                has_invalid_components,
117            },
118            thread_info,
119            webhook: None,
120            later_messages: later_messages::Info {
121                messages: vec![],
122                is_complete: false,
123                is_source_created: false,
124                is_later_message_sources_created: false,
125            },
126            response: None,
127            http,
128        })
129    }
130}