From f41846d88b93dfe3739c13a2a9ac94c6224cdd90 Mon Sep 17 00:00:00 2001 From: jude Date: Sat, 10 Oct 2026 20:03:15 +0100 Subject: [PATCH] Don't attempt to send "empty" embeds --- src/postman/sender.rs | 105 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/src/postman/sender.rs b/src/postman/sender.rs index d08b4c0..f9e2133 100644 --- a/src/postman/sender.rs +++ b/src/postman/sender.rs @@ -195,21 +195,100 @@ impl Embed { } pub fn has_content(&self) -> bool { - if self.title.is_empty() - && self.description.is_empty() - && self.image_url.is_none() - && self.thumbnail_url.is_none() - && self.footer.is_empty() - && self.footer_url.is_none() - && self.author.is_empty() - && self.author_url.is_none() - && self.fields.0.is_empty() - { - false - } else { - true + // Discord reports "description required" for empty embeds, but descriptions are optional. + // An author icon alone does not count as content; a footer icon does. + !self.title.trim().is_empty() + || !self.description.trim().is_empty() + || self.image_url.as_ref().is_some_and(|url| !url.trim().is_empty()) + || self.thumbnail_url.as_ref().is_some_and(|url| !url.trim().is_empty()) + || !self.footer.trim().is_empty() + || self.footer_url.as_ref().is_some_and(|url| !url.trim().is_empty()) + || !self.author.trim().is_empty() + || !self.fields.0.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn embed(description: &str) -> Embed { + Embed { + title: String::new(), + description: description.to_string(), + image_url: None, + thumbnail_url: None, + footer: String::new(), + footer_url: None, + author: String::new(), + author_url: None, + color: 0, + fields: Json(vec![]), } } + + #[test] + fn embed_without_description_can_have_content() { + let url = Some("https://example.com/image.png".to_string()); + let embeds = [ + Embed { title: "Reminder".to_string(), ..embed("") }, + Embed { image_url: url.clone(), ..embed("") }, + Embed { thumbnail_url: url.clone(), ..embed("") }, + Embed { footer: "Footer".to_string(), ..embed("") }, + Embed { footer_url: url, ..embed("") }, + Embed { author: "Author".to_string(), ..embed("") }, + Embed { + fields: Json(vec![EmbedField { + title: String::new(), + value: String::new(), + inline: false, + }]), + ..embed("") + }, + ]; + + for embed in embeds { + assert!(embed.has_content()); + } + } + + #[test] + fn empty_embed_after_substitution_has_no_content() { + let mut embed = embed("<>"); + embed.author_url = Some("https://example.com/image.png".to_string()); + embed.description = substitute(&embed.description); + + assert!(embed.description.is_empty()); + assert!(!embed.has_content()); + } + + #[test] + fn embed_with_only_description_has_content() { + assert!(embed("Remember to do the thing").has_content()); + } + + #[test] + fn empty_or_whitespace_embed_has_no_content() { + assert!(!embed("").has_content()); + let embed = Embed { + title: " \n\t ".to_string(), + image_url: Some(String::new()), + thumbnail_url: Some(" ".to_string()), + footer: " \n\t ".to_string(), + footer_url: Some(String::new()), + author: " \n\t ".to_string(), + color: 12345, + ..embed(" \n\t ") + }; + assert!(!embed.has_content()); + } + + #[test] + fn author_icon_alone_has_no_content() { + let embed = + Embed { author_url: Some("https://example.com/image.png".to_string()), ..embed("") }; + assert!(!embed.has_content()); + } } impl Into for Embed {