sparkle_impostor/
attachment_sticker.rs

1//! Handling the message having attachments or stickers
2
3#[cfg(feature = "reqwest")]
4use reqwest::Client;
5use twilight_model::channel::message::sticker::{MessageSticker, StickerFormatType};
6use twilight_validate::message::MESSAGE_CONTENT_LENGTH_MAX;
7
8use crate::{error::Error, MessageSource};
9
10/// Info about attachments and stickers in the message
11#[derive(Clone, Debug, PartialEq)]
12pub struct Info<'a> {
13    /// Stickers in the message
14    pub stickers: &'a [MessageSticker],
15    /// Attachments in the message
16    pub attachments: &'a [twilight_model::channel::Attachment],
17    /// Attachments to re-upload
18    #[cfg(feature = "upload")]
19    pub attachments_upload: Vec<twilight_model::http::attachment::Attachment>,
20}
21
22impl MessageSource<'_> {
23    /// Append links to the attachments into the message content
24    ///
25    /// If the attachment is an image, it's embedded in the client
26    ///
27    /// # Warnings
28    ///
29    /// The link will die after the source message is deleted
30    ///
31    /// # Errors
32    ///
33    /// Returns [`Error::ContentInvalid`] if the message content becomes
34    /// too long after adding the links
35    pub fn handle_attachment_link(mut self) -> Result<Self, Error> {
36        self.append_urls(
37            self.attachment_sticker_info
38                .attachments
39                .iter()
40                .map(|attachment| attachment.url.as_str()),
41        )?;
42
43        Ok(self)
44    }
45
46    /// Append links to the stickers into the message content
47    ///
48    /// The stickers are embedded in the client, but [`StickerFormatType::Apng`]
49    /// stickers aren't animated
50    ///
51    /// # Errors
52    ///
53    /// Returns [`Error::StickerLinkInvalid`] if a sticker's
54    /// [`StickerFormatType`] is [`StickerFormatType::Lottie`] or
55    /// [`StickerFormatType::Unknown`]
56    ///
57    /// Returns [`Error::ContentInvalid`] if the message content becomes
58    /// too long after adding the links
59    pub fn handle_sticker_link(mut self) -> Result<Self, Error> {
60        let mut sticker_urls = vec![];
61        for sticker in self.attachment_sticker_info.stickers {
62            sticker_urls.push(format!(
63                "https://cdn.discordapp.com/stickers/{}.{}",
64                sticker.id,
65                match sticker.format_type {
66                    StickerFormatType::Gif => "gif",
67                    StickerFormatType::Png | StickerFormatType::Apng => "png",
68                    _ => return Err(Error::StickerLinkInvalid),
69                }
70            ));
71        }
72
73        self.append_urls(sticker_urls.iter().map(String::as_str))?;
74
75        Ok(self)
76    }
77
78    #[allow(single_use_lifetimes)]
79    fn append_urls<'a>(
80        &mut self,
81        urls: impl Iterator<Item = &'a str> + Clone,
82    ) -> Result<(), Error> {
83        if self
84            .content
85            .chars()
86            .count()
87            .saturating_add(
88                urls.clone()
89                    // add 1 for newlines
90                    .map(|url| url.chars().count().saturating_add(1))
91                    .reduce(usize::saturating_add)
92                    .unwrap_or(0),
93            )
94            // add 1 for empty line between
95            .saturating_add(1)
96            > MESSAGE_CONTENT_LENGTH_MAX
97        {
98            return Err(Error::ContentInvalid);
99        }
100
101        self.content.push('\n');
102        for url in urls {
103            self.content.push('\n');
104            self.content.push_str(url);
105        }
106
107        Ok(())
108    }
109}
110
111#[cfg(feature = "upload")]
112impl<'a> MessageSource<'a> {
113    /// Re-upload the attachments
114    ///
115    /// This downloads and saves the attachments, they're later uploaded in
116    /// [`MessageSource::create`]
117    ///
118    /// # Warnings
119    ///
120    /// This is an expensive operation since it means downloading and uploading
121    /// up to 25 MBs
122    ///
123    /// # Errors
124    ///
125    /// Returns [`Error::AttachmentTooLarge`] if the combined size of the
126    /// attachments is over 25 MB
127    ///
128    /// Returns [`Error::Reqwest`] if downloading the attachments fails
129    pub async fn handle_attachment_upload(mut self) -> Result<MessageSource<'a>, Error> {
130        if self
131            .attachment_sticker_info
132            .attachments
133            .iter()
134            .map(|attachment| attachment.size)
135            .sum::<u64>()
136            > 25 * 1024 * 1024
137        {
138            return Err(Error::AttachmentTooLarge);
139        }
140
141        let client = Client::new();
142        for attachment in self.attachment_sticker_info.attachments {
143            self.attachment_sticker_info.attachments_upload.push(
144                twilight_model::http::attachment::Attachment {
145                    description: attachment.description.clone(),
146                    file: client
147                        .get(&attachment.url)
148                        .send()
149                        .await?
150                        .bytes()
151                        .await?
152                        .to_vec(),
153                    filename: attachment.filename.clone(),
154                    id: attachment.id.get(),
155                },
156            );
157        }
158
159        Ok(self)
160    }
161}