aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/context.rs97
-rw-r--r--src/model/channel.rs2
-rw-r--r--src/model/user.rs11
-rw-r--r--src/utils/builder/create_embed.rs327
-rw-r--r--src/utils/builder/create_message.rs26
-rw-r--r--src/utils/builder/mod.rs8
6 files changed, 442 insertions, 29 deletions
diff --git a/src/client/context.rs b/src/client/context.rs
index 6443efa..fe69a57 100644
--- a/src/client/context.rs
+++ b/src/client/context.rs
@@ -884,18 +884,99 @@ impl Context {
/// Sends a message to a [`Channel`].
///
- /// Note that often a nonce is not required and can be omitted in most
- /// situations.
+ /// Refer to the documentation for [`CreateMessage`] for more information
+ /// regarding message restrictions and requirements.
///
/// **Note**: Message contents must be under 2000 unicode code points.
///
/// # Example
///
+ /// Send a message with just the content `test`:
+ ///
/// ```rust,ignore
/// // assuming you are in a context
- /// let _ = context.send_message(message.channel_id, "Hello!", "", false);
+ /// let _ = context.send_message(message.channel_id, |f| f.content("test"));
/// ```
///
+ /// Send a message on `!ping` with a very descriptive [`Embed`]. This sends
+ /// a message content of `"Pong! Here's some info"`, with an embed with the
+ /// following attributes:
+ ///
+ /// - Dark gold in colour;
+ /// - A description of `"Information about the message just posted"`;
+ /// - A title of `"Message Information"`;
+ /// - A URL of `"https://rust-lang.org"`;
+ /// - An [author structure] containing an icon and the user's name;
+ /// - An inline [field structure] containing the message's content with a
+ /// label;
+ /// - An inline field containing the channel's name with a label;
+ /// - A footer containing the current user's icon and name, saying that the
+ /// information was generated by them.
+ ///
+ /// ```rust,no_run
+ /// use serenity::client::{STATE, Client, Context};
+ /// use serenity::model::{Channel, Message};
+ /// use serenity::utils::Colour;
+ /// use std::env;
+ ///
+ /// let mut client = Client::login_bot(&env::var("DISCORD_TOKEN").unwrap());
+ /// client.with_framework(|f| f
+ /// .configure(|c| c.prefix("~"))
+ /// .on("ping", ping));
+ ///
+ /// client.on_ready(|_context, ready| {
+ /// println!("{} is connected!", ready.user.name);
+ /// });
+ ///
+ /// let _ = client.start();
+ ///
+ /// fn ping(context: Context, message: Message, _arguments: Vec<String>) {
+ /// let state = STATE.lock().unwrap();
+ /// let ch = state.find_channel(message.channel_id);
+ /// let name = match ch {
+ /// Some(Channel::Public(ch)) => ch.name.clone(),
+ /// _ => "Unknown".to_owned(),
+ /// };
+ ///
+ /// let _ = context.send_message(message.channel_id, |m| m
+ /// .content("Pong! Here's some info")
+ /// .embed(|e| e
+ /// .colour(Colour::dark_gold())
+ /// .description("Information about the message just posted")
+ /// .title("Message information")
+ /// .url("https://rust-lang.org")
+ /// .author(|mut a| {
+ /// a = a.name(&message.author.name);
+ ///
+ /// if let Some(avatar) = message.author.avatar_url() {
+ /// a = a.icon_url(&avatar);
+ /// }
+ ///
+ /// a
+ /// })
+ /// .field(|f| f
+ /// .inline(true)
+ /// .name("Message content:")
+ /// .value(&message.content))
+ /// .field(|f| f
+ /// .inline(true)
+ /// .name("Channel name:")
+ /// .value(&name))
+ /// .footer(|mut f| {
+ /// f = f.text(&format!("Generated by {}", state.user.name));
+ ///
+ /// if let Some(avatar) = state.user.avatar_url() {
+ /// f = f.icon_url(&avatar);
+ /// }
+ ///
+ /// f
+ /// })));
+ /// }
+ /// ```
+ ///
+ /// Note that for most use cases, your embed layout will _not_ be this ugly.
+ /// This is an example of a very involved and conditional embed.
+ ///
/// # Errors
///
/// Returns a [`ClientError::MessageTooLong`] if the content of the message
@@ -904,13 +985,17 @@ impl Context {
///
/// [`Channel`]: ../model/enum.Channel.html
/// [`ClientError::MessageTooLong`]: enum.ClientError.html#variant.MessageTooLong
+ /// [`CreateMessage`]: ../utils/builder/struct.CreateMessage.html
+ /// [`Embed`]: ../model/struct.Embed.html
+ /// [author structure]: ../utils/builder/struct.CreateEmbedAuthor.html
+ /// [field structure]: ../utils/builder/struct.CreateEmbedField.html
pub fn send_message<C, F>(&self, channel_id: C, f: F) -> Result<Message>
where C: Into<ChannelId>, F: FnOnce(CreateMessage) -> CreateMessage {
let map = f(CreateMessage::default()).0;
- if let Some(ref content) = map.get(&"content".to_owned()) {
- if let &&Value::String(ref content) = content {
- if let Some(length_over) = Message::overflow_length(&content) {
+ if let Some(content) = map.get(&"content".to_owned()) {
+ if let Value::String(ref content) = *content {
+ if let Some(length_over) = Message::overflow_length(content) {
return Err(Error::Client(ClientError::MessageTooLong(length_over)));
}
}
diff --git a/src/model/channel.rs b/src/model/channel.rs
index 72a1f09..7348a1c 100644
--- a/src/model/channel.rs
+++ b/src/model/channel.rs
@@ -276,7 +276,7 @@ impl Embed {
#[cfg(feature = "methods")]
#[inline(always)]
pub fn fake<F>(f: F) -> Value where F: FnOnce(CreateEmbed) -> CreateEmbed {
- f(CreateEmbed::default()).0.build()
+ Value::Object(f(CreateEmbed::default()).0)
}
}
diff --git a/src/model/user.rs b/src/model/user.rs
index ce3d3a5..cd5072a 100644
--- a/src/model/user.rs
+++ b/src/model/user.rs
@@ -1,13 +1,14 @@
use std::fmt;
use super::utils::{into_map, into_string, remove, warn_field};
use super::{
+ CurrentUser,
FriendSourceFlags,
GuildContainer,
GuildId,
Mention,
RoleId,
UserSettings,
- User
+ User,
};
use ::internal::prelude::*;
use ::utils::decode_array;
@@ -22,6 +23,14 @@ use ::client::http;
#[cfg(feature = "state")]
use ::client::STATE;
+impl CurrentUser {
+ /// Returns the formatted URL of the user's icon, if one exists.
+ pub fn avatar_url(&self) -> Option<String> {
+ self.avatar.as_ref().map(|av|
+ format!(cdn_concat!("/avatars/{}/{}.jpg"), self.id, av))
+ }
+}
+
impl User {
/// Returns the formatted URL of the user's icon, if one exists.
pub fn avatar_url(&self) -> Option<String> {
diff --git a/src/utils/builder/create_embed.rs b/src/utils/builder/create_embed.rs
index 1abfb4e..4264175 100644
--- a/src/utils/builder/create_embed.rs
+++ b/src/utils/builder/create_embed.rs
@@ -1,42 +1,335 @@
+//! Developer note:
+//!
+//! This is a set of embed builders for rich embeds.
+//!
+//! These are used in the [`Context::send_message`] and
+//! [`ExecuteWebhook::embeds`] methods, both as part of builders.
+//!
+//! The only builder that should be exposed is [`CreateEmbed`]. The rest of
+//! these have no real reason for being exposed, but are for completeness' sake.
+//!
+//! Documentation for embeds can be found [here].
+//!
+//! [`Context::send_message`]: ../../client/struct.Context.html#method.send_message
+//! [`CreateEmbed`]: struct.CreateEmbed.html
+//! [`ExecuteWebhook::embeds`]: struct.ExecuteWebhook.html#method.embeds
+//! [here]: https://discordapp.com/developers/docs/resources/channel#embed-object
+
use serde_json::builder::ObjectBuilder;
+use serde_json::Value;
+use std::collections::BTreeMap;
use std::default::Default;
+use ::utils::Colour;
/// A builder to create a fake [`Embed`] object, for use with the
-/// [`ExecuteWebhook::embeds`] method.
+/// [`Context::send_message`] and [`ExecuteWebhook::embeds`] methods.
+///
+/// # Examples
///
-/// [`Embed`]: ../model/struct.Embed.html
+/// Refer to the documentation for [`Context::send_message`] for a very in-depth
+/// example on how to use this.
+///
+/// [`Context::send_message`]: ../../client/struct.Context.html#method.send_message
+/// [`Embed`]: ../../model/struct.Embed.html
/// [`ExecuteWebhook::embeds`]: struct.ExecuteWebhook.html#method.embeds
-pub struct CreateEmbed(pub ObjectBuilder);
+pub struct CreateEmbed(pub BTreeMap<String, Value>);
impl CreateEmbed {
+ /// Set the author of the embed.
+ ///
+ /// Refer to the documentation for [`CreateEmbedAuthor`] for more
+ /// information.
+ ///
+ /// [`CreateEmbedAuthor`]: struct.CreateEmbedAuthor.html
+ pub fn author<F>(mut self, f: F) -> Self
+ where F: FnOnce(CreateEmbedAuthor) -> CreateEmbedAuthor {
+ let author = f(CreateEmbedAuthor::default()).0.build();
+
+ self.0.insert("author".to_owned(), author);
+
+ CreateEmbed(self.0)
+ }
+
/// Set the colour of the left-hand side of the embed.
- pub fn colour(self, colour: u64) -> Self {
- CreateEmbed(self.0.insert("color", colour))
+ ///
+ /// This is an alias of [`colour`].
+ ///
+ /// [`colour`]: #method.colour
+ pub fn color<C: Into<Colour>>(self, colour: C) -> Self {
+ self.colour(colour.into())
}
- /// Set the description.
- pub fn description(self, description: &str) -> Self {
- CreateEmbed(self.0.insert("description", description))
+ /// Set the colour of the left-hand side of the embed.
+ pub fn colour<C: Into<Colour>>(mut self, colour: C) -> Self {
+ self.0.insert("color".to_owned(), Value::U64(colour.into().value as u64));
+
+ CreateEmbed(self.0)
+ }
+
+ /// Set the description of the embed.
+ pub fn description(mut self, description: &str) -> Self {
+ self.0.insert("description".to_owned(), Value::String(description.to_owned()));
+
+ CreateEmbed(self.0)
+ }
+
+ /// Set a field. Note that this will not overwrite other fields, and will
+ /// add to them.
+ ///
+ /// Refer to the documentation for [`CreateEmbedField`] for more
+ /// information.
+ ///
+ /// [`CreateEmbedField`]: struct.CreateEmbedField.html
+ pub fn field<F>(mut self, f: F) -> Self
+ where F: FnOnce(CreateEmbedField) -> CreateEmbedField {
+ let field = f(CreateEmbedField::default()).0.build();
+
+ {
+ let key = "fields".to_owned();
+
+ let entry = self.0.remove(&key).unwrap_or_else(|| Value::Array(vec![]));
+ let mut arr = match entry {
+ Value::Array(inner) => inner,
+ _ => {
+ // The type of `entry` should always be a `Value::Array`.
+ //
+ // Theoretically this never happens, but you never know.
+ //
+ // In the event that it does, just return the current value.
+ return CreateEmbed(self.0);
+ },
+ };
+ arr.push(field);
+
+ self.0.insert("fields".to_owned(), Value::Array(arr));
+ }
+
+ CreateEmbed(self.0)
+ }
+
+ /// Set the footer of the embed.
+ ///
+ /// Refer to the documentation for [`CreateEmbedFooter`] for more
+ /// information.
+ ///
+ /// [`CreateEmbedFooter`]: struct.CreateEmbedFooter.html
+ pub fn footer<F>(mut self, f: F) -> Self
+ where F: FnOnce(CreateEmbedFooter) -> CreateEmbedFooter {
+ let footer = f(CreateEmbedFooter::default()).0.build();
+
+ self.0.insert("footer".to_owned(), footer);
+
+ CreateEmbed(self.0)
+ }
+
+ /// Set the thumbnail of the embed.
+ ///
+ /// Refer to the documentation for [`CreateEmbedThumbnail`] for more
+ /// information.
+ ///
+ /// [`CreateEmbedThumbnail`]: struct.CreateEmbedThumbnail.html
+ pub fn thumbnail<F>(mut self, f: F) -> Self
+ where F: FnOnce(CreateEmbedThumbnail) -> CreateEmbedThumbnail {
+ let thumbnail = f(CreateEmbedThumbnail::default()).0.build();
+
+ self.0.insert("thumbnail".to_owned(), thumbnail);
+
+ CreateEmbed(self.0)
}
/// Set the timestamp.
- pub fn timestamp(self, timestamp: &str) -> Self {
- CreateEmbed(self.0.insert("timestamp", timestamp))
+ pub fn timestamp(mut self, timestamp: &str) -> Self {
+ self.0.insert("timestamp".to_owned(), Value::String(timestamp.to_owned()));
+
+ CreateEmbed(self.0)
}
- /// Set the title.
- pub fn title(self, title: &str) -> Self {
- CreateEmbed(self.0.insert("title", title))
+ /// Set the title of the embed.
+ pub fn title(mut self, title: &str) -> Self {
+ self.0.insert("title".to_owned(), Value::String(title.to_owned()));
+
+ CreateEmbed(self.0)
}
- /// Set the URL.
- pub fn url(self, url: &str) -> Self {
- CreateEmbed(self.0.insert("url", url))
+ /// Set the URL to direct to when clicking on the title.
+ pub fn url(mut self, url: &str) -> Self {
+ self.0.insert("url".to_owned(), Value::String(url.to_owned()));
+
+ CreateEmbed(self.0)
}
}
impl Default for CreateEmbed {
+ /// Creates a builder with default values, setting the `type` to `rich`.
fn default() -> CreateEmbed {
- CreateEmbed(ObjectBuilder::new())
+ let mut map = BTreeMap::new();
+ map.insert("type".to_owned(), Value::String("rich".to_owned()));
+
+ CreateEmbed(map)
+ }
+}
+
+/// A builder to create a fake [`Embed`] object's author, for use with the
+/// [`CreateEmbed::author`] method.
+///
+/// Requires that you specify a [`name`].
+///
+/// [`Embed`]: ../../model/struct.Embed.html
+/// [`CreateEmbed::author`]: struct.CreateEmbed.html#method.author
+/// [`name`]: #method.name
+pub struct CreateEmbedAuthor(pub ObjectBuilder);
+
+impl CreateEmbedAuthor {
+ /// Set the URL of the author's icon.
+ pub fn icon_url(self, icon_url: &str) -> Self {
+ CreateEmbedAuthor(self.0.insert("icon_url", icon_url))
+ }
+
+ /// Set the author's name.
+ pub fn name(self, name: &str) -> Self {
+ CreateEmbedAuthor(self.0.insert("name", name))
+ }
+
+ /// Set the author's URL.
+ pub fn url(self, url: &str) -> Self {
+ CreateEmbedAuthor(self.0.insert("url", url))
+ }
+}
+
+impl Default for CreateEmbedAuthor {
+ fn default() -> CreateEmbedAuthor {
+ CreateEmbedAuthor(ObjectBuilder::new())
+ }
+}
+
+/// A builder to create a fake [`Embed`] object's field, for use with the
+/// [`CreateEmbed::field`] method.
+///
+/// This does not require any field be set. `inline` is set to `true` by
+/// default.
+///
+/// [`Embed`]: ../../model/struct.Embed.html
+/// [`CreateEmbed::field`]: struct.CreateEmbed.html#method.field
+pub struct CreateEmbedField(pub ObjectBuilder);
+
+impl CreateEmbedField {
+ /// Set whether the field is inlined.
+ pub fn inline(self, inline: bool) -> Self {
+ CreateEmbedField(self.0.insert("inline", inline))
+ }
+
+ /// Set the field's name.
+ pub fn name(self, name: &str) -> Self {
+ CreateEmbedField(self.0.insert("name", name))
+ }
+
+ /// Set the field's value.
+ pub fn value(self, value: &str) -> Self {
+ CreateEmbedField(self.0.insert("value", value))
+ }
+}
+
+impl Default for CreateEmbedField {
+ /// Creates a builder with default values, setting the value of `inline` to
+ /// `true`.
+ fn default() -> CreateEmbedField {
+ CreateEmbedField(ObjectBuilder::new())
+ }
+}
+
+/// A builder to create a fake [`Embed`] object's footer, for use with the
+/// [`CreateEmbed::footer`] method.
+///
+/// This does not require any field be set.
+///
+/// [`Embed`]: ../../model/struct.Embed.html
+/// [`CreateEmbed::footer`]: struct.CreateEmbed.html#method.footer
+pub struct CreateEmbedFooter(pub ObjectBuilder);
+
+impl CreateEmbedFooter {
+ /// Set the icon URL's value. This only supports HTTP(S).
+ pub fn icon_url(self, icon_url: &str) -> Self {
+ CreateEmbedFooter(self.0.insert("icon_url", icon_url))
+ }
+
+ /// Set the footer's text.
+ pub fn text(self, text: &str) -> Self {
+ CreateEmbedFooter(self.0.insert("text", text))
+ }
+}
+
+impl Default for CreateEmbedFooter {
+ fn default() -> CreateEmbedFooter {
+ CreateEmbedFooter(ObjectBuilder::new())
+ }
+}
+
+/// A builder to create a fake [`Embed`] object's thumbnail, for use with the
+/// [`CreateEmbed::thumbnail`] method.
+///
+/// Requires that you specify a [`url`].
+///
+/// [`Embed`]: ../../model/struct.Embed.html
+/// [`CreateEmbed::thumbnail`]: struct.CreateEmbed.html#method.thumbnail
+/// [`url`]: #method.url
+pub struct CreateEmbedThumbnail(pub ObjectBuilder);
+
+impl CreateEmbedThumbnail {
+ /// Set the height of the thumbnail, in pixels.
+ pub fn height(self, height: u64) -> Self {
+ CreateEmbedThumbnail(self.0.insert("height", height))
+ }
+
+ /// Set the URL of the thumbnail. This only supports HTTP(S).
+ ///
+ /// _Must_ be specified.
+ pub fn url(self, url: &str) -> Self {
+ CreateEmbedThumbnail(self.0.insert("url", url))
+ }
+
+ /// Set the width of the thumbnail, in pixels.
+ pub fn width(self, width: u64) -> Self {
+ CreateEmbedThumbnail(self.0.insert("width", width))
+ }
+}
+
+impl Default for CreateEmbedThumbnail {
+ fn default() -> CreateEmbedThumbnail {
+ CreateEmbedThumbnail(ObjectBuilder::new())
+ }
+}
+
+/// A builder to create a fake [`Embed`] object's video, for use with the
+/// [`CreateEmbed::video`] method.
+///
+/// Requires that you specify a [`url`].
+///
+/// [`Embed`]: ../../model/struct.Embed.html
+/// [`CreateEmbed::video`]: struct.CreateEmbed.html#method.video
+/// [`url`]: #method.url
+pub struct CreateEmbedVideo(pub ObjectBuilder);
+
+impl CreateEmbedVideo {
+ /// Set the height of the video, in pixels.
+ pub fn height(self, height: u64) -> Self {
+ CreateEmbedVideo(self.0.insert("height", height))
+ }
+
+ /// Set the source URL of the video.
+ ///
+ /// _Must_ be specified.
+ pub fn url(self, url: &str) -> Self {
+ CreateEmbedVideo(self.0.insert("url", url))
+ }
+
+ /// Set the width of the video, in pixels.
+ pub fn width(self, width: &str) -> Self {
+ CreateEmbedVideo(self.0.insert("width", width))
+ }
+}
+
+impl Default for CreateEmbedVideo {
+ fn default() -> CreateEmbedVideo {
+ CreateEmbedVideo(ObjectBuilder::new())
}
}
diff --git a/src/utils/builder/create_message.rs b/src/utils/builder/create_message.rs
index dc601a4..9bbc461 100644
--- a/src/utils/builder/create_message.rs
+++ b/src/utils/builder/create_message.rs
@@ -1,11 +1,16 @@
use serde_json::Value;
use std::collections::BTreeMap;
use std::default::Default;
+use super::CreateEmbed;
/// A builder to specify the contents of an [`http::create_message`] request,
/// primarily meant for use through [`Context::send_message`].
///
-/// `content` is the only required field.
+/// There are two situations where different field requirements are present:
+///
+/// 1. When sending an [`embed`], no other field is required;
+/// 2. Otherwise, [`content`] is the only required field that is required to be
+/// set.
///
/// Note that if you only need to send the content of a message, without
/// specifying other fields, then [`Context::say`] may be a more preferable
@@ -13,7 +18,7 @@ use std::default::Default;
///
/// # Examples
///
-/// Sending a message with a content of `test` and applying text-to-speech:
+/// Sending a message with a content of `"test"` and applying text-to-speech:
///
/// ```rust,ignore
/// // assuming you are in a context
@@ -24,6 +29,8 @@ use std::default::Default;
///
/// [`Context::say`]: ../../client/struct.Context.html#method.say
/// [`Context::send_message`]: ../../client/struct.Context.html#method.send_message
+/// [`content`]: #method.content
+/// [`embed`]: #method.embed
/// [`http::create_message`]: ../../client/http/fn.create_message.html
pub struct CreateMessage(pub BTreeMap<String, Value>);
@@ -37,6 +44,16 @@ impl CreateMessage {
CreateMessage(self.0)
}
+ /// Set an embed for the message.
+ pub fn embed<F>(mut self, f: F) -> Self
+ where F: FnOnce(CreateEmbed) -> CreateEmbed {
+ let embed = Value::Object(f(CreateEmbed::default()).0);
+
+ self.0.insert("embed".to_owned(), embed);
+
+ CreateMessage(self.0)
+ }
+
/// Set the nonce. This is used for validation of a sent message. You most
/// likely don't need to worry about this.
///
@@ -60,8 +77,11 @@ impl CreateMessage {
}
impl Default for CreateMessage {
- /// Creates a map for sending a [`Message`], setting `tts` to `false` by
+ /// Creates a map for sending a [`Message`], setting [`tts`] to `false` by
/// default.
+ ///
+ /// [`Message`]: ../../model/struct.Message.html
+ /// [`tts`]: #method.tts
fn default() -> CreateMessage {
let mut map = BTreeMap::default();
map.insert("tts".to_owned(), Value::Bool(false));
diff --git a/src/utils/builder/mod.rs b/src/utils/builder/mod.rs
index 91ad940..d4da891 100644
--- a/src/utils/builder/mod.rs
+++ b/src/utils/builder/mod.rs
@@ -16,7 +16,13 @@ mod edit_role;
mod execute_webhook;
mod get_messages;
-pub use self::create_embed::CreateEmbed;
+pub use self::create_embed::{
+ CreateEmbed,
+ CreateEmbedAuthor,
+ CreateEmbedField,
+ CreateEmbedThumbnail,
+ CreateEmbedVideo,
+};
pub use self::create_invite::CreateInvite;
pub use self::create_message::CreateMessage;
pub use self::edit_channel::EditChannel;