sparkle_impostor/later_messages.rs
1//! Handling the message not being the last one in the channel
2
3use twilight_model::channel::Message;
4#[cfg(doc)]
5use twilight_model::guild::Permissions;
6use twilight_validate::message::MESSAGE_CONTENT_LENGTH_MAX;
7
8use crate::{error::Error, thread, MessageSource};
9
10/// Info about the later messages in the channel
11#[derive(Debug, Clone, PartialEq)]
12pub struct Info {
13 /// Messages sent later
14 ///
15 /// Has to be an owned value because some methods set it from owned values
16 pub messages: Vec<Message>,
17 /// Whether there are no more messages
18 pub is_complete: bool,
19 /// Whether [`MessageSource::create`] was called
20 pub is_source_created: bool,
21 /// Whether [`MessageSource::later_messages`] or
22 /// [`MessageSource::later_messages_batched`] was called
23 pub is_later_message_sources_created: bool,
24}
25
26impl<'a> MessageSource<'a> {
27 /// Check if this is in the last `n` messages in the channel, return
28 /// [`Error::SourceAboveLimit`] if not
29 ///
30 /// Make sure the bot has these additional permissions
31 /// - [`Permissions::READ_MESSAGE_HISTORY`]
32 /// - [`Permissions::VIEW_CHANNEL`]
33 ///
34 /// # Warnings
35 ///
36 /// If [`MessageSource::create`] was called before this method, don't
37 /// account for that message when setting the limit
38 ///
39 /// If the bot doesn't have [`Permissions::READ_MESSAGE_HISTORY`], it'll act
40 /// as if this is the last message, since that's what Discord responds with
41 ///
42 /// # Errors
43 ///
44 /// Returns [`Error::SourceAboveLimit`] if the message isn't in the last `n`
45 /// messages
46 ///
47 /// Returns [`Error::Http`] if getting channel messages fails
48 ///
49 /// Returns [`Error::DeserializeBody`] if deserializing channel messages
50 /// fails
51 pub async fn check_is_in_last(&mut self, n: u16) -> Result<(), Error> {
52 self.set_later_messages(Some(n)).await?;
53
54 Ok(())
55 }
56
57 /// Return [`MessageSource`] for messages sent after this
58 ///
59 /// Make sure the bot has these additional permissions
60 /// - [`Permissions::READ_MESSAGE_HISTORY`]
61 /// - [`Permissions::VIEW_CHANNEL`]
62 ///
63 /// Returned message sources don't implicitly call the same methods as
64 /// `self`, this is intentional so that you can choose what to handle
65 /// yourself, if you want to handle each one the same way, simply extract
66 /// it to a function
67 ///
68 /// # Warnings
69 ///
70 /// This method is potentially very expensive unless
71 /// [`MessageSource::check_is_in_last`] was called
72 ///
73 /// If the bot doesn't have [`Permissions::READ_MESSAGE_HISTORY`], it'll
74 /// always return an empty vector, since that's what Discord responds with
75 ///
76 /// Should not be combined with [`MessageSource::later_messages_batched`]
77 ///
78 /// # Errors
79 ///
80 /// The vector element will be an error if the message can't be resent (See
81 /// [`MessageSource::from_message`])
82 ///
83 /// Returns [`Error::Http`] if getting channel messages fails
84 ///
85 /// Returns [`Error::DeserializeBody`] if deserializing channel messages
86 /// fails
87 pub async fn later_messages(&mut self) -> Result<Vec<Result<MessageSource<'_>, Error>>, Error> {
88 self.set_later_messages(None).await?;
89
90 Ok(self.later_message_sources())
91 }
92
93 /// Return [`MessageSource`] for messages sent after this after combining
94 /// messages from the same author to the same message
95 ///
96 /// This combines the messages' content separated with a newline, it's
97 /// provided to reduce the number of webhook executions
98 ///
99 /// See [`MessageSource::later_messages`] for more
100 ///
101 /// # Warnings
102 ///
103 /// Should not be combined with [`MessageSource::later_messages`]
104 ///
105 /// # Errors
106 ///
107 /// Returns [`Error::Http`] if getting channel messages fails
108 ///
109 /// Returns [`Error::DeserializeBody`] if deserializing channel messages
110 /// fails
111 pub async fn later_messages_batched(
112 &mut self,
113 ) -> Result<Vec<Result<MessageSource<'_>, Error>>, Error> {
114 self.set_later_messages(None).await?;
115
116 // clone to another vec because removing elements from the vec is more expensive
117 let mut messages_batched = vec![];
118
119 for message in self.later_messages.messages.clone() {
120 let Some(last_message) = messages_batched.last_mut() else {
121 messages_batched.push(message);
122 continue;
123 };
124
125 if last_message.author.id == message.author.id
126 && last_message
127 .content
128 .chars()
129 .count()
130 .saturating_add(message.content.chars().count())
131 < MESSAGE_CONTENT_LENGTH_MAX
132 // not <= because we push '\n' too
133 {
134 last_message.content.push('\n');
135 last_message.content.push_str(&message.content);
136 } else {
137 messages_batched.push(message);
138 }
139 }
140 self.later_messages.messages = messages_batched;
141
142 Ok(self.later_message_sources())
143 }
144
145 async fn set_later_messages(&mut self, limit: Option<u16>) -> Result<(), Error> {
146 loop {
147 if let Some(limit_inner) = limit {
148 if self.later_messages.messages.len() >= usize::from(limit_inner) {
149 return Err(Error::SourceAboveLimit(limit_inner));
150 }
151 }
152 if self.later_messages.is_complete {
153 return Ok(());
154 }
155
156 let mut message_batch = self
157 .http
158 .channel_messages(self.source_thread_id.unwrap_or(self.source_channel_id))
159 .limit(limit.unwrap_or(100).min(100))?
160 .after(
161 self.later_messages
162 .messages
163 .last()
164 .map_or(self.source_id, |message: &Message| message.id),
165 )
166 .await?
167 .models()
168 .await?;
169
170 for message in &mut message_batch {
171 message.guild_id = Some(self.guild_id);
172 }
173
174 self.later_messages.is_complete =
175 message_batch.is_empty() || message_batch.len() % 100 != 0;
176
177 self.later_messages.messages.extend(
178 message_batch
179 .into_iter()
180 // skip message sent in self.create
181 .skip(usize::from(
182 self.later_messages.is_source_created
183 && self.source_thread_id == self.thread_info.id()
184 && self.channel_id == self.source_channel_id
185 && self.later_messages.messages.is_empty(),
186 ))
187 .rev()
188 // skip the system message when used in threads
189 .skip(usize::from(self.thread_info.id().is_some())),
190 );
191 }
192 }
193
194 fn later_message_sources(&mut self) -> Vec<Result<MessageSource<'_>, Error>> {
195 self.later_messages.is_later_message_sources_created = true;
196
197 self.later_messages
198 .messages
199 .iter()
200 .map(|message| {
201 MessageSource::from_message(message, self.http).map(|mut source| {
202 source.thread_info = self
203 .thread_info
204 .id()
205 .map_or(thread::Info::NotIn, thread::Info::In);
206 source.channel_id = self.channel_id;
207 source
208 })
209 })
210 .collect()
211 }
212}