diff options
| author | Austin Hellyer <[email protected]> | 2016-11-13 08:22:24 -0800 |
|---|---|---|
| committer | Austin Hellyer <[email protected]> | 2016-11-13 08:22:24 -0800 |
| commit | f633d1c9603079f584f4f715b308b33c0750ad3a (patch) | |
| tree | f77bc0b630731676be880b11c24b90cffef8c537 /src/utils/builder | |
| parent | Don't overflow on message length check (diff) | |
| download | serenity-f633d1c9603079f584f4f715b308b33c0750ad3a.tar.xz serenity-f633d1c9603079f584f4f715b308b33c0750ad3a.zip | |
Move the builders to the utils
The builders aren't a large enough portion of the library to deserve
their own root-level module, so move them to the `utils` module.
Additionally, split them into separate files, as the library will be
receiving more builders and the single-file pattern was getting rather
large.
Diffstat (limited to 'src/utils/builder')
| -rw-r--r-- | src/utils/builder/create_embed.rs | 42 | ||||
| -rw-r--r-- | src/utils/builder/create_invite.rs | 71 | ||||
| -rw-r--r-- | src/utils/builder/edit_channel.rs | 77 | ||||
| -rw-r--r-- | src/utils/builder/edit_guild.rs | 53 | ||||
| -rw-r--r-- | src/utils/builder/edit_member.rs | 35 | ||||
| -rw-r--r-- | src/utils/builder/edit_profile.rs | 93 | ||||
| -rw-r--r-- | src/utils/builder/edit_role.rs | 104 | ||||
| -rw-r--r-- | src/utils/builder/execute_webhook.rs | 55 | ||||
| -rw-r--r-- | src/utils/builder/get_messages.rs | 81 | ||||
| -rw-r--r-- | src/utils/builder/mod.rs | 26 |
10 files changed, 637 insertions, 0 deletions
diff --git a/src/utils/builder/create_embed.rs b/src/utils/builder/create_embed.rs new file mode 100644 index 0000000..1abfb4e --- /dev/null +++ b/src/utils/builder/create_embed.rs @@ -0,0 +1,42 @@ +use serde_json::builder::ObjectBuilder; +use std::default::Default; + +/// A builder to create a fake [`Embed`] object, for use with the +/// [`ExecuteWebhook::embeds`] method. +/// +/// [`Embed`]: ../model/struct.Embed.html +/// [`ExecuteWebhook::embeds`]: struct.ExecuteWebhook.html#method.embeds +pub struct CreateEmbed(pub ObjectBuilder); + +impl CreateEmbed { + /// Set the colour of the left-hand side of the embed. + pub fn colour(self, colour: u64) -> Self { + CreateEmbed(self.0.insert("color", colour)) + } + + /// Set the description. + pub fn description(self, description: &str) -> Self { + CreateEmbed(self.0.insert("description", description)) + } + + /// Set the timestamp. + pub fn timestamp(self, timestamp: &str) -> Self { + CreateEmbed(self.0.insert("timestamp", timestamp)) + } + + /// Set the title. + pub fn title(self, title: &str) -> Self { + CreateEmbed(self.0.insert("title", title)) + } + + /// Set the URL. + pub fn url(self, url: &str) -> Self { + CreateEmbed(self.0.insert("url", url)) + } +} + +impl Default for CreateEmbed { + fn default() -> CreateEmbed { + CreateEmbed(ObjectBuilder::new()) + } +} diff --git a/src/utils/builder/create_invite.rs b/src/utils/builder/create_invite.rs new file mode 100644 index 0000000..55cc1bd --- /dev/null +++ b/src/utils/builder/create_invite.rs @@ -0,0 +1,71 @@ +use serde_json::builder::ObjectBuilder; +use serde_json::Value; +use std::default::Default; + +/// A builder to create a [`RichInvite`] for use via [`Context::create_invite`]. +/// +/// This is a structured and cleaner way of creating an invite, as all +/// parameters are optional. +/// +/// # Examples +/// +/// Create an invite with a max age of 3600 seconds and 10 max uses: +/// +/// ```rust,ignore +/// use serenity::Client; +/// use std::env; +/// +/// let mut client = Client::login_bot(&env::var("DISCORD_BOT_TOKEN").unwrap()); +/// +/// client.on_message(|context, message| { +/// if message.content == "!invite" { +/// let invite = context.create_invite(message.channel_id, |i| i +/// .max_age(3600) +/// .max_uses(10)); +/// } +/// }); +/// ``` +/// +/// [`Context::create_invite`]: ../client/struct.Context.html#method.create_invite +/// [`RichInvite`]: ../model/struct.Invite.html +pub struct CreateInvite(pub ObjectBuilder); + +impl CreateInvite { + /// The duration that the invite will be valid for. + /// + /// Set to `0` for an invite which does not expire after an amount of time. + /// + /// Defaults to `86400`, or 24 hours. + pub fn max_age(self, max_age: u64) -> Self { + CreateInvite(self.0.insert("max_age", max_age)) + } + + /// The number of uses that the invite will be valid for. + /// + /// Set to `0` for an invite which does not expire after a number of uses. + /// + /// Defaults to `0`. + pub fn max_uses(self, max_uses: u64) -> Self { + CreateInvite(self.0.insert("max_uses", max_uses)) + } + + /// Whether an invite grants a temporary membership. + /// + /// Defaults to `false`. + pub fn temporary(self, temporary: bool) -> Self { + CreateInvite(self.0.insert("temporary", temporary)) + } + + /// Whether or not to try to reuse a similar invite. + /// + /// Defaults to `false`. + pub fn unique(self, unique: bool) -> Self { + CreateInvite(self.0.insert("unique", unique)) + } +} + +impl Default for CreateInvite { + fn default() -> CreateInvite { + CreateInvite(ObjectBuilder::new().insert("validate", Value::Null)) + } +} diff --git a/src/utils/builder/edit_channel.rs b/src/utils/builder/edit_channel.rs new file mode 100644 index 0000000..7225902 --- /dev/null +++ b/src/utils/builder/edit_channel.rs @@ -0,0 +1,77 @@ +use serde_json::builder::ObjectBuilder; +use std::default::Default; + +/// A builder to edit a [`PublicChannel`] for use via one of a couple methods. +/// +/// These methods are: +/// +/// - [`Context::edit_channel`] +/// - [`PublicChannel::edit`] +/// +/// Defaults are not directly provided by the builder itself. +/// +/// # Examples +/// +/// Edit a channel, providing a new name and topic: +/// +/// ```rust,ignore +/// // assuming a channel has already been bound +/// if let Err(why) = channel::edit(|c| c.name("new name").topic("a test topic")) { +/// // properly handle the error +/// } +/// ``` +/// +/// [`Context::edit_channel`]: ../client/struct.Context.html#method.edit_channel +/// [`PublicChannel`]: ../model/struct.PublicChannel.html +/// [`PublicChannel::edit`]: ../model/struct.PublicChannel.html#method.edit +pub struct EditChannel(pub ObjectBuilder); + +impl EditChannel { + /// The bitrate of the channel in bits. + /// + /// This is for [voice] channels only. + /// + /// [voice]: ../model/enum.ChannelType.html#variant.Voice + pub fn bitrate(self, bitrate: u64) -> Self { + EditChannel(self.0.insert("bitrate", bitrate)) + } + + /// The name of the channel. + /// + /// Must be between 2 and 100 characters long. + pub fn name(self, name: &str) -> Self { + EditChannel(self.0.insert("name", name)) + } + + /// The position of the channel in the channel list. + pub fn position(self, position: u64) -> Self { + EditChannel(self.0.insert("position", position)) + } + + /// The topic of the channel. Can be empty. + /// + /// Must be between 0 and 1024 characters long. + /// + /// This is for [text] channels only. + /// + /// [text]: ../model/enum.ChannelType.html#variant.Text + pub fn topic(self, topic: &str) -> Self { + EditChannel(self.0.insert("topic", topic)) + } + + /// The number of users that may be in the channel simultaneously. + /// + /// This is for [voice] channels only. + /// + /// [voice]: ../model/enum.ChannelType.html#variant.Voice + pub fn user_limit(self, user_limit: u64) -> Self { + EditChannel(self.0.insert("user_limit", user_limit)) + } +} + +impl Default for EditChannel { + /// Creates a builder with no default parameters. + fn default() -> EditChannel { + EditChannel(ObjectBuilder::new()) + } +} diff --git a/src/utils/builder/edit_guild.rs b/src/utils/builder/edit_guild.rs new file mode 100644 index 0000000..3ea9aaa --- /dev/null +++ b/src/utils/builder/edit_guild.rs @@ -0,0 +1,53 @@ +use serde_json::builder::ObjectBuilder; +use serde_json::Value; +use std::default::Default; +use ::model::{ChannelId, Region, VerificationLevel}; + +pub struct EditGuild(pub ObjectBuilder); + +impl EditGuild { + pub fn afk_channel<C: Into<ChannelId>>(self, channel: Option<C>) -> Self { + EditGuild(match channel { + Some(channel) => self.0.insert("afk_channel_id", channel.into().0), + None => self.0.insert("afk-channel_id", Value::Null), + }) + } + + pub fn afk_timeout(self, timeout: u64) -> Self { + EditGuild(self.0.insert("afk_timeout", timeout)) + } + + pub fn icon(self, icon: Option<&str>) -> Self { + EditGuild(self.0 + .insert("icon", + icon.map_or_else(|| Value::Null, + |x| Value::String(x.to_owned())))) + } + + pub fn name(self, name: &str) -> Self { + EditGuild(self.0.insert("name", name)) + } + + pub fn region(self, region: Region) -> Self { + EditGuild(self.0.insert("region", region.name())) + } + + pub fn splash(self, splash: Option<&str>) -> Self { + EditGuild(self.0 + .insert("splash", + splash.map_or_else(|| Value::Null, + |x| Value::String(x.to_owned())))) + } + + pub fn verification_level<V>(self, verification_level: V) -> Self + where V: Into<VerificationLevel> { + EditGuild(self.0.insert("verification_level", + verification_level.into().num())) + } +} + +impl Default for EditGuild { + fn default() -> EditGuild { + EditGuild(ObjectBuilder::new()) + } +} diff --git a/src/utils/builder/edit_member.rs b/src/utils/builder/edit_member.rs new file mode 100644 index 0000000..c26cb84 --- /dev/null +++ b/src/utils/builder/edit_member.rs @@ -0,0 +1,35 @@ +use serde_json::builder::ObjectBuilder; +use std::default::Default; +use ::model::{ChannelId, RoleId}; + +pub struct EditMember(pub ObjectBuilder); + +impl EditMember { + pub fn deafen(self, deafen: bool) -> Self { + EditMember(self.0.insert("deaf", deafen)) + } + + pub fn mute(self, mute: bool) -> Self { + EditMember(self.0.insert("mute", mute)) + } + + pub fn nickname(self, nickname: &str) -> Self { + EditMember(self.0.insert("nick", nickname)) + } + + pub fn roles(self, roles: &[RoleId]) -> Self { + EditMember(self.0 + .insert_array("roles", + |a| roles.iter().fold(a, |a, id| a.push(id.0)))) + } + + pub fn voice_channel<C: Into<ChannelId>>(self, channel_id: C) -> Self { + EditMember(self.0.insert("channel_id", channel_id.into().0)) + } +} + +impl Default for EditMember { + fn default() -> EditMember { + EditMember(ObjectBuilder::new()) + } +} diff --git a/src/utils/builder/edit_profile.rs b/src/utils/builder/edit_profile.rs new file mode 100644 index 0000000..c83f049 --- /dev/null +++ b/src/utils/builder/edit_profile.rs @@ -0,0 +1,93 @@ +use serde_json::builder::ObjectBuilder; +use serde_json::Value; +use std::default::Default; + +pub struct EditProfile(pub ObjectBuilder); + +impl EditProfile { + /// Sets the avatar of the current user. `None` can be passed to remove an + /// avatar. + /// + /// A base64-encoded string is accepted as the avatar content. + /// + /// # Examples + /// + /// A utility method - [`utils::read_message`] - is provided to read an + /// image from a file and return its contents in base64-encoded form: + /// + /// ```rust,ignore + /// use serenity::utils; + /// + /// // assuming you are in a context + /// + /// let base64 = match utils::read_image("./my_image.jpg") { + /// Ok(base64) => base64, + /// Err(why) => { + /// println!("Error reading image: {:?}", why); + /// + /// return; + /// }, + /// }; + /// + /// let _ = context.edit_profile(|profile| { + /// profile.avatar(Some(base64)) + /// }); + /// ``` + /// + /// [`utils::read_image`]: ../utils/fn.read_image.html + pub fn avatar(self, icon: Option<&str>) -> Self { + EditProfile(self.0 + .insert("avatar", + icon.map_or_else(|| Value::Null, + |x| Value::String(x.to_owned())))) + } + + /// Modifies the current user's email address. + /// + /// Note that when modifying the email address, the current password must + /// also be [provided]. + /// + /// No validation is performed on this by the library. + /// + /// **Note**: This can only be used by user accounts. + /// + /// [provided]: #method.password + pub fn email(self, email: &str) -> Self { + EditProfile(self.0.insert("email", email)) + } + + /// Modifies the current user's password. + /// + /// Note that when modifying the password, the current password must also be + /// [provided]. + /// + /// [provided]: #method.password + pub fn new_password(self, new_password: &str) -> Self { + EditProfile(self.0.insert("new_password", new_password)) + } + + /// Used for providing the current password as verification when + /// [modifying the password] or [modifying the associated email address]. + /// + /// [modifying the password]: #method.new_password + /// [modifying the associated email address]: #method.email + pub fn password(self, password: &str) -> Self { + EditProfile(self.0.insert("password", password)) + } + + /// Modifies the current user's username. + /// + /// When modifying the username, if another user has the same _new_ username + /// and current discriminator, a new unique discriminator will be assigned. + /// If there are no available discriminators with the requested username, + /// an error will occur. + pub fn username(self, username: &str) -> Self { + EditProfile(self.0.insert("username", username)) + } +} + +impl Default for EditProfile { + fn default() -> EditProfile { + EditProfile(ObjectBuilder::new()) + } +} diff --git a/src/utils/builder/edit_role.rs b/src/utils/builder/edit_role.rs new file mode 100644 index 0000000..f4fa7ef --- /dev/null +++ b/src/utils/builder/edit_role.rs @@ -0,0 +1,104 @@ +use serde_json::builder::ObjectBuilder; +use std::default::Default; +use ::model::{Permissions, Role, permissions}; + +/// A builer to create or edit a [`Role`] for use via a number of model and +/// context methods. +/// +/// These are: +/// +/// - [`Context::create_role`] +/// - [`Context::edit_role`] +/// - [`LiveGuild::create_role`] +/// - [`Role::edit`] +/// +/// Defaults are provided for each parameter on role creation. +/// +/// # Examples +/// +/// Create a hoisted, mentionable role named "a test role": +/// +/// ```rust,ignore +/// // assuming you are in a `context` and a `guild_id` has been bound +/// let role = context.create_role(guild_id, |r| r +/// .hoist(true) +/// .mentionable(true) +/// .name("a test role")); +/// ``` +/// +/// [`Context::create_role`]: ../client/struct.Context.html#method.create_role +/// [`Context::edit_role`]: ../client/struct.Context.html#method.edit_role +/// [`LiveGuild::create_role`]: ../model/struct.LiveGuild.html#method.create_role +/// [`Role`]: ../model/struct.Role.html +/// [`Role::edit`]: ../model/struct.Role.html#method.edit +pub struct EditRole(pub ObjectBuilder); + +impl EditRole { + /// Creates a new builder with the values of the given [`Role`]. + pub fn new(role: &Role) -> Self { + EditRole(ObjectBuilder::new() + .insert("color", role.colour) + .insert("hoist", role.hoist) + .insert("managed", role.managed) + .insert("mentionable", role.mentionable) + .insert("name", &role.name) + .insert("permissions", role.permissions.bits()) + .insert("position", role.position)) + } + + /// Sets the colour of the role. + pub fn colour(self, colour: u64) -> Self { + EditRole(self.0.insert("color", colour)) + } + + /// Whether or not to hoist the role above lower-positioned role in the user + /// list. + pub fn hoist(self, hoist: bool) -> Self { + EditRole(self.0.insert("hoist", hoist)) + } + + /// Whether or not to make the role mentionable, notifying its users. + pub fn mentionable(self, mentionable: bool) -> Self { + EditRole(self.0.insert("mentionable", mentionable)) + } + + /// The name of the role to set. + pub fn name(self, name: &str) -> Self { + EditRole(self.0.insert("name", name)) + } + + /// The set of permissions to assign the role. + pub fn permissions(self, permissions: Permissions) -> Self { + EditRole(self.0.insert("permissions", permissions.bits())) + } + + /// The position to assign the role in the role list. This correlates to the + /// role's position in the user list. + pub fn position(self, position: u8) -> Self { + EditRole(self.0.insert("position", position)) + } +} + +impl Default for EditRole { + /// Creates a builder with default parameters. + /// + /// The defaults are: + /// + /// - **color**: 10070709 + /// - **hoist**: false + /// - **mentionable**: false + /// - **name**: new role + /// - **permissions**: the [general permissions set] + /// - **position**: 1 + /// + /// [general permissions set]: ../model/permissions/fn.general.html + fn default() -> EditRole { + EditRole(ObjectBuilder::new() + .insert("color", 10070709) + .insert("hoist", false) + .insert("mentionable", false) + .insert("name", String::from("new role")) + .insert("permissions", permissions::general().bits()) + .insert("position", 1)) + } +} diff --git a/src/utils/builder/execute_webhook.rs b/src/utils/builder/execute_webhook.rs new file mode 100644 index 0000000..d2a14aa --- /dev/null +++ b/src/utils/builder/execute_webhook.rs @@ -0,0 +1,55 @@ +use serde_json::builder::ObjectBuilder; +use serde_json::Value; +use std::default::Default; + +/// A builder to create the inner content of a [`Webhook`]'s execution. +/// +/// This is a structured way of cleanly creating the inner execution payload, +/// to reduce potential argument counts. +/// +/// Refer to the documentation for [`execute_webhook`] on restrictions with +/// execution payloads and its fields. +/// +/// [`Webhook`]: ../model/struct.Webhook.html +/// [`execute_webhook`]: ../client/http/fn.execute_webhook.html +pub struct ExecuteWebhook(pub ObjectBuilder); + +impl ExecuteWebhook { + /// Override the default avatar of the webhook with an image URL. + pub fn avatar_url(self, avatar_url: &str) -> Self { + ExecuteWebhook(self.0.insert("avatar_url", avatar_url)) + } + + /// Set the content of the message. + pub fn content(self, content: &str) -> Self { + ExecuteWebhook(self.0.insert("content", content)) + } + + // Set the embeds associated with the message. + pub fn embeds(self, embeds: Vec<Value>) -> Self { + ExecuteWebhook(self.0.insert("embeds", embeds)) + } + + /// Whether the message is a text-to-speech message. + /// + /// Think carefully before setting this to `true`. + pub fn tts(self, tts: bool) -> Self { + ExecuteWebhook(self.0.insert("tts", tts)) + } + + /// Override the default username of the webhook. + pub fn username(self, username: &str) -> Self { + ExecuteWebhook(self.0.insert("username", username)) + } +} + +impl Default for ExecuteWebhook { + /// Returns a default set of values for a [`Webhook`] execution. + /// + /// The only default value is `tts` being set to `true`. In the event that + /// there is a bug that Discord defaults `tts` to `true`, at least + /// serenity.rs won't be a part of it. + fn default() -> ExecuteWebhook { + ExecuteWebhook(ObjectBuilder::new().insert("tts", false)) + } +} diff --git a/src/utils/builder/get_messages.rs b/src/utils/builder/get_messages.rs new file mode 100644 index 0000000..4fdadff --- /dev/null +++ b/src/utils/builder/get_messages.rs @@ -0,0 +1,81 @@ +use std::collections::BTreeMap; +use std::default::Default; +use ::model::MessageId; + +/// Builds a request for a request to the API to retrieve messages. +/// +/// This can have 2 different sets of parameters. The first set is around where +/// to get the messages: +/// +/// - `after` +/// - `around` +/// - `before` +/// - `most_recent` +/// +/// These can not be mixed, and the first in the list alphabetically will be +/// used. If one is not specified, `most_recent` will be used. +/// +/// The fourth parameter is to specify the number of messages to retrieve. This +/// does not _need_ to be called and defaults to a value of 50. +/// +/// This should be used only for retrieving messages; see +/// [`Client::get_messages`] for examples. +/// +/// [`Client::get_messages`]: ../../client/struct.Client.html#method.get_messages +pub struct GetMessages(pub BTreeMap<String, u64>); + +impl GetMessages { + /// Indicates to retrieve the messages after a specific message, given by + /// its Id. + pub fn after<M: Into<MessageId>>(mut self, message_id: M) -> Self { + self.0.insert("after".to_owned(), message_id.into().0); + + self + } + + /// Indicates to retrieve the messages _around_ a specific message in either + /// direction (before+after) the given message. + pub fn around<M: Into<MessageId>>(mut self, message_id: M) -> Self { + self.0.insert("around".to_owned(), message_id.into().0); + + self + } + + /// Indicates to retrieve the messages before a specific message, given by + /// its Id. + pub fn before<M: Into<MessageId>>(mut self, message_id: M) -> Self { + self.0.insert("before".to_owned(), message_id.into().0); + + self + } + + /// The maximum number of messages to retrieve for the query. + /// + /// If this is not specified, a default value of 50 is used. + /// + /// **Note**: This field is capped to 100 messages due to a Discord + /// limitation. If an amount larger than 100 is supplied, it will be + /// reduced. + pub fn limit(mut self, limit: u64) -> Self { + self.0.insert("limit".to_owned(), if limit > 100 { + 100 + } else { + limit + }); + + self + } + + /// This is a function that is here for completeness. You do not need to + /// call this - except to clear previous calls to `after`, `around`, and + /// `before` - as it is the default value. + pub fn most_recent(self) -> Self { + self + } +} + +impl Default for GetMessages { + fn default() -> GetMessages { + GetMessages(BTreeMap::default()) + } +} diff --git a/src/utils/builder/mod.rs b/src/utils/builder/mod.rs new file mode 100644 index 0000000..cd830fa --- /dev/null +++ b/src/utils/builder/mod.rs @@ -0,0 +1,26 @@ +//! A set of builders used to make using methods on certain structs simpler to +//! use. +//! +//! These are used when not all parameters are required, all parameters are +//! optional, and/or sane default values for required parameters can be applied +//! by a builder. + +mod create_embed; +mod create_invite; +mod edit_channel; +mod edit_guild; +mod edit_member; +mod edit_profile; +mod edit_role; +mod execute_webhook; +mod get_messages; + +pub use self::create_embed::CreateEmbed; +pub use self::create_invite::CreateInvite; +pub use self::edit_channel::EditChannel; +pub use self::edit_guild::EditGuild; +pub use self::edit_member::EditMember; +pub use self::edit_profile::EditProfile; +pub use self::edit_role::EditRole; +pub use self::execute_webhook::ExecuteWebhook; +pub use self::get_messages::GetMessages; |