pub mod image_source;
mod author;
mod field;
mod footer;
pub use self::{
author::EmbedAuthorBuilder, field::EmbedFieldBuilder, footer::EmbedFooterBuilder,
image_source::ImageSource,
};
use twilight_model::{
channel::message::embed::{
Embed, EmbedAuthor, EmbedField, EmbedFooter, EmbedImage, EmbedThumbnail,
},
util::Timestamp,
};
use twilight_validate::embed::{EmbedValidationError, embed as validate_embed};
#[derive(Clone, Debug, Eq, PartialEq)]
#[must_use = "must be built into an embed"]
pub struct EmbedBuilder(Embed);
impl EmbedBuilder {
pub fn new() -> Self {
EmbedBuilder(Embed {
author: None,
color: None,
description: None,
fields: Vec::new(),
footer: None,
image: None,
kind: "rich".to_owned(),
provider: None,
thumbnail: None,
timestamp: None,
title: None,
url: None,
video: None,
})
}
#[allow(clippy::missing_const_for_fn)]
#[must_use = "should be used as part of something like a message"]
pub fn build(self) -> Embed {
self.0
}
pub fn validate(self) -> Result<Self, EmbedValidationError> {
#[allow(clippy::question_mark)]
if let Err(source) = validate_embed(&self.0) {
return Err(source);
}
Ok(self)
}
pub fn author(mut self, author: impl Into<EmbedAuthor>) -> Self {
self.0.author = Some(author.into());
self
}
pub const fn color(mut self, color: u32) -> Self {
self.0.color = Some(color);
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.0.description = Some(description.into());
self
}
pub fn field(mut self, field: impl Into<EmbedField>) -> Self {
self.0.fields.push(field.into());
self
}
pub fn footer(mut self, footer: impl Into<EmbedFooter>) -> Self {
self.0.footer = Some(footer.into());
self
}
#[allow(clippy::missing_const_for_fn)]
pub fn image(mut self, image_source: ImageSource) -> Self {
self.0.image = Some(EmbedImage {
height: None,
proxy_url: None,
url: image_source.0,
width: None,
});
self
}
#[allow(clippy::missing_const_for_fn)]
pub fn thumbnail(mut self, image_source: ImageSource) -> Self {
self.0.thumbnail = Some(EmbedThumbnail {
height: None,
proxy_url: None,
url: image_source.0,
width: None,
});
self
}
pub const fn timestamp(mut self, timestamp: Timestamp) -> Self {
self.0.timestamp = Some(timestamp);
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.0.title = Some(title.into());
self
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.0.url = Some(url.into());
self
}
}
impl Default for EmbedBuilder {
fn default() -> Self {
Self::new()
}
}
impl From<Embed> for EmbedBuilder {
fn from(value: Embed) -> Self {
Self(Embed {
kind: "rich".to_owned(),
..value
})
}
}
impl TryFrom<EmbedBuilder> for Embed {
type Error = EmbedValidationError;
fn try_from(builder: EmbedBuilder) -> Result<Self, Self::Error> {
Ok(builder.validate()?.build())
}
}
#[cfg(test)]
mod tests {
use super::*;
use static_assertions::assert_impl_all;
use std::fmt::Debug;
assert_impl_all!(EmbedBuilder: Clone, Debug, Eq, PartialEq, Send, Sync);
assert_impl_all!(Embed: TryFrom<EmbedBuilder>);
#[test]
fn builder() {
let footer_image = ImageSource::url(
"https://raw.githubusercontent.com/twilight-rs/twilight/main/logo.png",
)
.unwrap();
let timestamp = Timestamp::from_secs(1_580_608_922).expect("non zero");
let embed = EmbedBuilder::new()
.color(0x00_43_ff)
.description("Description")
.timestamp(timestamp)
.footer(EmbedFooterBuilder::new("Warn").icon_url(footer_image))
.field(EmbedFieldBuilder::new("name", "title").inline())
.build();
let expected = Embed {
author: None,
color: Some(0x00_43_ff),
description: Some("Description".to_string()),
fields: [EmbedField {
inline: true,
name: "name".to_string(),
value: "title".to_string(),
}]
.to_vec(),
footer: Some(EmbedFooter {
icon_url: Some(
"https://raw.githubusercontent.com/twilight-rs/twilight/main/logo.png"
.to_string(),
),
proxy_icon_url: None,
text: "Warn".to_string(),
}),
image: None,
kind: "rich".to_string(),
provider: None,
thumbnail: None,
timestamp: Some(timestamp),
title: None,
url: None,
video: None,
};
assert_eq!(embed, expected);
}
}