1#![warn(
2 clippy::cargo,
3 clippy::nursery,
4 clippy::pedantic,
5 clippy::allow_attributes,
6 clippy::allow_attributes_without_reason,
7 clippy::arithmetic_side_effects,
8 clippy::as_underscore,
9 clippy::assertions_on_result_states,
10 clippy::clone_on_ref_ptr,
11 clippy::create_dir,
12 clippy::dbg_macro,
13 clippy::default_numeric_fallback,
14 clippy::empty_drop,
15 clippy::empty_structs_with_brackets,
16 clippy::exit,
17 clippy::filetype_is_file,
18 clippy::float_cmp_const,
19 clippy::fn_to_numeric_cast_any,
20 clippy::format_push_string,
21 clippy::if_then_some_else_none,
22 clippy::indexing_slicing,
23 clippy::integer_division,
24 clippy::large_include_file,
25 clippy::let_underscore_must_use,
26 clippy::lossy_float_literal,
27 clippy::mem_forget,
28 clippy::mixed_read_write_in_expression,
29 clippy::mod_module_files,
30 clippy::multiple_unsafe_ops_per_block,
31 clippy::mutex_atomic,
32 clippy::rc_buffer,
33 clippy::rc_mutex,
34 clippy::rest_pat_in_fully_bound_structs,
35 clippy::same_name_method,
36 clippy::semicolon_inside_block,
37 clippy::shadow_reuse,
38 clippy::shadow_same,
39 clippy::shadow_unrelated,
40 clippy::str_to_string,
41 clippy::string_add,
42 clippy::string_slice,
43 clippy::string_to_string,
44 clippy::suspicious_xor_used_as_pow,
45 clippy::tests_outside_test_module,
46 clippy::try_err,
47 clippy::unnecessary_safety_comment,
48 clippy::unnecessary_safety_doc,
49 clippy::unneeded_field_pattern,
50 clippy::unseparated_literal_suffix,
51 clippy::verbose_file_reads,
52 rustdoc::missing_crate_level_docs,
53 rustdoc::private_doc_tests,
54 absolute_paths_not_starting_with_crate,
55 elided_lifetimes_in_paths,
56 explicit_outlives_requirements,
57 keyword_idents,
58 let_underscore_drop,
59 macro_use_extern_crate,
60 meta_variable_misuse,
61 missing_abi,
62 missing_copy_implementations,
63 missing_debug_implementations,
64 missing_docs,
65 non_ascii_idents,
66 noop_method_call,
67 pointer_structural_match,
68 rust_2021_incompatible_or_patterns,
69 rust_2021_prefixes_incompatible_syntax,
70 rust_2021_prelude_collisions,
71 single_use_lifetimes,
72 trivial_casts,
73 trivial_numeric_casts,
74 unreachable_pub,
75 unsafe_code,
76 unsafe_op_in_unsafe_fn,
77 unused_crate_dependencies,
78 unused_extern_crates,
79 unused_import_braces,
80 unused_lifetimes,
81 unused_macro_rules,
82 unused_qualifications,
83 unused_tuple_struct_fields,
84 variant_size_differences,
85 )]
91#![allow(clippy::redundant_pub_crate)]
92#![doc = include_str!("../README.md")]
93
94#[cfg(test)]
95use anyhow as _;
96#[cfg(test)]
97use dotenvy as _;
98#[cfg(test)]
99use tokio as _;
100use twilight_http::{request::channel::webhook::ExecuteWebhookAndWait, Client};
101#[cfg(doc)]
102use twilight_model::guild::Permissions;
103use twilight_model::{
104 channel::{
105 message::{Embed, MessageFlags},
106 Message,
107 },
108 id::{
109 marker::{ChannelMarker, EmojiMarker, GuildMarker, MessageMarker, WebhookMarker},
110 Id,
111 },
112};
113
114use crate::error::Error;
115
116pub mod attachment_sticker;
117pub mod avatar;
118pub mod component;
119mod constructor;
120mod delete;
121pub mod error;
122pub mod later_messages;
123pub mod reaction;
124pub mod reference;
125pub mod response;
126pub mod thread;
127mod username;
128
129#[derive(Debug)]
152pub struct MessageSource<'a> {
153 pub source_id: Id<MessageMarker>,
155 pub source_channel_id: Id<ChannelMarker>,
157 pub source_thread_id: Option<Id<ChannelMarker>>,
159 pub content: String,
161 pub embeds: Vec<Embed>,
163 pub tts: bool,
165 pub flags: Option<MessageFlags>,
167 pub channel_id: Id<ChannelMarker>,
171 pub guild_id: Id<GuildMarker>,
173 pub guild_emoji_ids: Option<Vec<Id<EmojiMarker>>>,
177 pub username: String,
179 pub webhook_name: String,
181 pub avatar_info: avatar::Info,
183 pub reference_info: reference::Info<'a>,
185 pub reaction_info: reaction::Info<'a>,
187 pub attachment_sticker_info: attachment_sticker::Info<'a>,
189 pub component_info: component::Info,
191 pub thread_info: thread::Info,
193 pub later_messages: later_messages::Info,
195 pub webhook: Option<(Id<WebhookMarker>, String)>,
197 pub response: Option<response::MaybeDeserialized<Message>>,
201 pub http: &'a Client,
203}
204
205impl<'a> MessageSource<'a> {
206 pub async fn create(mut self) -> Result<MessageSource<'a>, Error> {
242 self.set_webhook().await?;
243 self.avatar_info.set_url();
244
245 for i in 0..=5_u8 {
246 match self.webhook_exec()?.await {
247 Ok(response) => {
248 self.response = Some(response::MaybeDeserialized::Response(response));
249 break;
250 }
251 Err(err)
252 if matches!(
253 err.kind(),
254 twilight_http::error::ErrorType::Response {
255 error: twilight_http::api_error::ApiError::Ratelimited(_),
256 ..
257 }
258 ) =>
259 {
260 if i == 5 {
261 return Err(Error::Http(err));
262 }
263 continue;
264 }
265 Err(err) => return Err(Error::Http(err)),
266 }
267 }
268
269 self.later_messages.is_source_created = true;
270
271 Ok(self)
272 }
273
274 #[must_use]
278 #[allow(clippy::missing_const_for_fn)]
279 pub fn webhook_name(mut self, name: String) -> Self {
280 self.webhook_name = name;
281 self
282 }
283
284 async fn set_webhook(&mut self) -> Result<(), Error> {
285 if self.webhook.is_some() {
286 return Ok(());
287 }
288
289 let webhook = if let Some(webhook) = self
290 .http
291 .channel_webhooks(self.channel_id)
292 .await?
293 .models()
294 .await?
295 .into_iter()
296 .find(|webhook| {
297 webhook.token.is_some() && webhook.name.as_ref() == Some(&self.webhook_name)
298 }) {
299 webhook
300 } else {
301 self.http
302 .create_webhook(self.channel_id, &self.webhook_name)?
303 .await?
304 .model()
305 .await?
306 };
307 self.webhook = Some((webhook.id, webhook.token.unwrap()));
308
309 Ok(())
310 }
311
312 fn webhook_exec(&self) -> Result<ExecuteWebhookAndWait<'_>, Error> {
313 let (webhook_id, webhook_token) = self.webhook.as_ref().unwrap();
314
315 let mut execute_webhook = self
316 .http
317 .execute_webhook(*webhook_id, webhook_token)
318 .content(&self.content)?
319 .embeds(&self.embeds)?
320 .components(&self.component_info.url_components)?
321 .username(&self.username)?
322 .avatar_url(self.avatar_info.url.as_ref().unwrap())
323 .tts(self.tts);
324
325 match &self.thread_info {
326 thread::Info::In(thread_id) => execute_webhook = execute_webhook.thread_id(*thread_id),
327 thread::Info::CreatedPost(channel) => {
328 execute_webhook = execute_webhook.thread_name(channel.name.as_ref().unwrap());
329 }
330 _ => {}
331 }
332
333 if let Some(flags) = self.flags {
334 execute_webhook = execute_webhook.flags(flags);
335 }
336
337 #[cfg(feature = "upload")]
338 {
339 execute_webhook =
340 execute_webhook.attachments(&self.attachment_sticker_info.attachments_upload)?;
341 }
342
343 Ok(execute_webhook.wait())
345 }
346
347 async fn set_guild_emojis(&mut self) -> Result<(), Error> {
348 if self.guild_emoji_ids.is_some() {
349 return Ok(());
350 }
351
352 self.guild_emoji_ids = Some(
353 self.http
354 .emojis(self.guild_id)
355 .await?
356 .models()
357 .await?
358 .into_iter()
359 .map(|emoji| emoji.id)
360 .collect(),
361 );
362
363 Ok(())
364 }
365}