Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Builder paradigm overhaul #1967

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2a432f2
Rework CreateSticker
mkrasnitski Jun 9, 2022
c0251d9
Rework CreateThread
mkrasnitski Jun 10, 2022
084228c
Rework CreateScheduledEvent
mkrasnitski Jun 16, 2022
416e310
Unify `execute` and `execute_with_cache`
mkrasnitski Jun 16, 2022
e047752
Move `#[must_use]` to struct definition
mkrasnitski Jun 17, 2022
df0bd6e
Replace `AsRef<Http>` with concrete `&Http` in a few places
mkrasnitski Jun 17, 2022
35ea3f8
Fix compilation with `builder` enabled but `model` disabled
mkrasnitski Jun 17, 2022
72ca5cc
Workaround lifetime bug by getting rid of `&'static str`
mkrasnitski Jun 17, 2022
c9c389f
Small fixes, left some model methods marked async
mkrasnitski Jun 18, 2022
dcf7421
Rework CreateChannel
mkrasnitski Jun 18, 2022
4098cd2
Move `user_has_perms_cache` into top-level `utils` module
mkrasnitski Jun 18, 2022
62d5f1e
Rework CreateInvite
mkrasnitski Jun 18, 2022
6eb7c53
Rework AddMember
mkrasnitski Jun 20, 2022
05b531c
Replace `&self`/`*self` with `self` on Id types since they're `Copy`
mkrasnitski Jun 20, 2022
11b2f04
Docs tweaks and forgot a few `#[must_use]`
mkrasnitski Jun 20, 2022
0ceb304
Rework CreateStageInstance
mkrasnitski Jun 20, 2022
6abc0f2
When possible, wrap an Id method instead of constructing a builder di…
mkrasnitski Jun 20, 2022
6c2334f
Fix doctests
mkrasnitski Jun 20, 2022
bd43101
Rework CreateMessage
mkrasnitski Jun 22, 2022
b6a5342
Add length checks to `CreateMessage` and `CreateEmbed`
mkrasnitski Jun 22, 2022
f1d539b
Rework CreateInteractionResponse
mkrasnitski Jun 23, 2022
586fdea
Rework CreateInterationResponseData
mkrasnitski Jun 23, 2022
b447343
Rework CreateInteractionResponseFollowup
mkrasnitski Jun 24, 2022
b5cb6a0
Rework CreateAutocompleteResponse
mkrasnitski Jun 24, 2022
1175cdc
Fix examples 03, 14, and 17
mkrasnitski Jun 24, 2022
ada28b6
Rework CreateEmbed, CreateEmbedAuthor, and CreateEmbedFooter
mkrasnitski Jun 25, 2022
f543d42
Adjust docs and fix clippy warnings
mkrasnitski Jun 25, 2022
9490d06
Rework GetMessages
mkrasnitski Jun 26, 2022
6384bd0
Rework CreateBotAuthParameters
mkrasnitski Jun 26, 2022
d5c295e
Rework CreateWebhook
mkrasnitski Jun 26, 2022
1cbc014
Forgot some `#[serde(skip)]`s
mkrasnitski Jun 26, 2022
2f18fe7
Rework EditWebhook
mkrasnitski Jun 26, 2022
5175f12
Add additional avatar/icon/image methods for better feature coverage
mkrasnitski Jun 27, 2022
806accb
Rework EditWebhookMessage
mkrasnitski Jun 27, 2022
ef373c0
Rework ExecuteWebhook
mkrasnitski Jun 28, 2022
3de8517
Rework EditProfile
mkrasnitski Jun 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions examples/e03_struct_utilities/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ impl EventHandler for Handler {
// In this case, you can direct message a User directly by simply
// calling a method on its instance, with the content of the
// message.
let dm = msg.author.dm(&context, |m| m.content("Hello!")).await;

if let Err(why) = dm {
println!("Error when direct messaging user: {:?}", why);
match msg.author.dm(&context).await {
Ok(create_message) => {
let msg = create_message.content("Hello!").execute(&context).await;
if let Err(why) = msg {
println!("Error when direct messaging user: {:?}", why);
}
},
Err(why) => println!("Error when direct messaging user: {:?}", why),
}
}
}
Expand Down
38 changes: 20 additions & 18 deletions examples/e09_create_message_builder/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::env;

use serenity::async_trait;
use serenity::builder::{CreateEmbed, CreateEmbedFooter};
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
use serenity::model::Timestamp;
Expand All @@ -16,26 +17,27 @@ impl EventHandler for Handler {
// using a builder syntax.
// This example will create a message that says "Hello, World!", with an embed that has
// a title, description, an image, three fields, and a footer.
let footer = CreateEmbedFooter::default().text("This is a footer");
let embed = CreateEmbed::default()
.title("This is a title")
.description("This is a description")
.image("attachment://ferris_eyes.png")
.fields(vec![
("This is the first field", "This is a field body", true),
("This is the second field", "Both fields are inline", true),
])
.field("This is the third field", "This is not an inline field", false)
.footer(footer)
// Add a timestamp for the current time
// This also accepts a rfc3339 Timestamp
.timestamp(Timestamp::now());
let msg = msg
.channel_id
.send_message(&ctx.http, |m| {
m.content("Hello, World!")
.embed(|e| {
e.title("This is a title")
.description("This is a description")
.image("attachment://ferris_eyes.png")
.fields(vec![
("This is the first field", "This is a field body", true),
("This is the second field", "Both fields are inline", true),
])
.field("This is the third field", "This is not an inline field", false)
.footer(|f| f.text("This is a footer"))
// Add a timestamp for the current time
// This also accepts a rfc3339 Timestamp
.timestamp(Timestamp::now())
})
.add_file("./ferris_eyes.png")
})
.send_message()
.content("Hello, World!")
.embed(embed)
.add_file("./ferris_eyes.png")
.execute(&ctx.http)
.await;

if let Err(why) = msg {
Expand Down
31 changes: 14 additions & 17 deletions examples/e13_parallel_loops/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::time::Duration;

use chrono::offset::Utc;
use serenity::async_trait;
use serenity::builder::CreateEmbed;
use serenity::gateway::ActivityData;
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
Expand Down Expand Up @@ -79,23 +80,19 @@ async fn log_system_load(ctx: Arc<Context>) {

// We can use ChannelId directly to send a message to a specific channel; in this case, the
// message would be sent to the #testing channel on the discord server.
let message = ChannelId::new(381926291785383946)
.send_message(&ctx, |m| {
m.embed(|e| {
e.title("System Resource Load")
.field("CPU Load Average", &format!("{:.2}%", cpu_load.one * 10.0), false)
.field(
"Memory Usage",
&format!(
"{:.2} MB Free out of {:.2} MB",
mem_use.free as f32 / 1000.0,
mem_use.total as f32 / 1000.0
),
false,
)
})
})
.await;
let embed = CreateEmbed::default()
.title("System Resource Load")
.field("CPU Load Average", format!("{:.2}%", cpu_load.one * 10.0), false)
.field(
"Memory Usage",
format!(
"{:.2} MB Free out of {:.2} MB",
mem_use.free as f32 / 1000.0,
mem_use.total as f32 / 1000.0
),
false,
);
let message = ChannelId::new(381926291785383946).send_message().embed(embed).execute(ctx).await;
if let Err(why) = message {
eprintln!("Error sending message: {:?}", why);
};
Expand Down
11 changes: 6 additions & 5 deletions examples/e14_slash_commands/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::env;

use serenity::async_trait;
use serenity::builder::CreateInteractionResponseData;
use serenity::model::application::command::{Command, CommandOptionType};
use serenity::model::application::interaction::application_command::CommandDataOptionValue;
use serenity::model::application::interaction::{Interaction, InteractionResponseType};
Expand Down Expand Up @@ -56,12 +57,12 @@ impl EventHandler for Handler {
_ => "not implemented :(".to_string(),
};

let data = CreateInteractionResponseData::default().content(content);
if let Err(why) = command
.create_interaction_response(&ctx.http, |response| {
response
.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|message| message.content(content))
})
.create_interaction_response()
.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(data)
.execute(&ctx.http)
.await
{
println!("Cannot respond to slash command: {}", why);
Expand Down
47 changes: 24 additions & 23 deletions examples/e17_message_components/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{env, fmt};

use dotenv::dotenv;
use serenity::async_trait;
use serenity::builder::{CreateActionRow, CreateButton, CreateSelectMenu, CreateSelectMenuOption};
use serenity::builder::*;
use serenity::client::{Context, EventHandler};
use serenity::futures::StreamExt;
use serenity::model::application::component::ButtonStyle;
Expand Down Expand Up @@ -172,10 +172,10 @@ impl EventHandler for Handler {
// Ask the user for its favorite animal
let m = msg
.channel_id
.send_message(&ctx, |m| {
m.content("Please select your favorite animal")
.components(|c| c.add_action_row(Animal::action_row()))
})
.send_message()
.content("Please select your favorite animal")
.components(|c| c.add_action_row(Animal::action_row()))
.execute(&ctx)
.await
.unwrap();

Expand All @@ -195,14 +195,15 @@ impl EventHandler for Handler {
let animal = Animal::from_str(mci.data.values.get(0).unwrap()).unwrap();

// Acknowledge the interaction and edit the message
mci.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage).interaction_response_data(|d| {
d.content(format!("You chose: **{}**\nNow choose a sound!", animal))
.components(|c| c.add_action_row(Sound::action_row()))
})
})
.await
.unwrap();
let data = CreateInteractionResponseData::default()
.content(format!("You chose: **{}**\nNow choose a sound!", animal))
.components(|c| c.add_action_row(Sound::action_row()));
mci.create_interaction_response()
.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(data)
.execute(&ctx)
.await
.unwrap();

// Wait for multiple interactions

Expand All @@ -211,18 +212,18 @@ impl EventHandler for Handler {

while let Some(mci) = cib.next().await {
let sound = Sound::from_str(&mci.data.custom_id).unwrap();
let data = CreateInteractionResponseData::default()
// Make the message hidden for other users by setting `ephemeral(true)`.
.ephemeral(true)
.content(format!("The **{}** says __{}__", animal, sound));
// Acknowledge the interaction and send a reply
mci.create_interaction_response(&ctx, |r| {
mci.create_interaction_response()
// This time we dont edit the message but reply to it
r.kind(InteractionResponseType::ChannelMessageWithSource).interaction_response_data(
|d| {
// Make the message hidden for other users by setting `ephemeral(true)`.
d.ephemeral(true).content(format!("The **{}** says __{}__", animal, sound))
},
)
})
.await
.unwrap();
.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(data)
.execute(&ctx)
.await
.unwrap();
}

// Delete the orig message or there will be dangling components
Expand Down
56 changes: 50 additions & 6 deletions src/builder/add_member.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
#[cfg(feature = "http")]
use crate::http::Http;
#[cfg(feature = "http")]
use crate::internal::prelude::*;
use crate::model::id::RoleId;
#[cfg(feature = "http")]
use crate::model::prelude::*;

/// A builder to add parameters when using [`GuildId::add_member`].
///
/// [`GuildId::add_member`]: crate::model::id::GuildId::add_member
#[derive(Clone, Debug, Default, Serialize)]
#[derive(Clone, Debug, Serialize)]
#[must_use]
pub struct AddMember {
#[cfg(feature = "http")]
#[serde(skip)]
guild_id: GuildId,
#[cfg(feature = "http")]
#[serde(skip)]
user_id: UserId,

#[serde(skip_serializing_if = "Option::is_none")]
access_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -18,10 +32,27 @@ pub struct AddMember {
}

impl AddMember {
pub fn new(
#[cfg(feature = "http")] guild_id: GuildId,
#[cfg(feature = "http")] user_id: UserId,
) -> Self {
Self {
#[cfg(feature = "http")]
guild_id,
#[cfg(feature = "http")]
user_id,
access_token: None,
nick: None,
roles: None,
mute: None,
deaf: None,
}
}

/// Sets the OAuth2 access token for this request.
///
/// Requires the access token to have the `guilds.join` scope granted.
pub fn access_token(&mut self, access_token: impl Into<String>) -> &mut Self {
pub fn access_token(mut self, access_token: impl Into<String>) -> Self {
self.access_token = Some(access_token.into());
self
}
Expand All @@ -31,7 +62,7 @@ impl AddMember {
/// Requires the [Manage Nicknames] permission.
///
/// [Manage Nicknames]: crate::model::permissions::Permissions::MANAGE_NICKNAMES
pub fn nickname(&mut self, nickname: impl Into<String>) -> &mut Self {
pub fn nickname(mut self, nickname: impl Into<String>) -> Self {
self.nick = Some(nickname.into());
self
}
Expand All @@ -41,7 +72,7 @@ impl AddMember {
/// Requires the [Manage Roles] permission.
///
/// [Manage Roles]: crate::model::permissions::Permissions::MANAGE_ROLES
pub fn roles(&mut self, roles: impl IntoIterator<Item = impl Into<RoleId>>) -> &mut Self {
pub fn roles(mut self, roles: impl IntoIterator<Item = impl Into<RoleId>>) -> Self {
self.roles = Some(roles.into_iter().map(Into::into).collect());
self
}
Expand All @@ -51,7 +82,7 @@ impl AddMember {
/// Requires the [Mute Members] permission.
///
/// [Mute Members]: crate::model::permissions::Permissions::MUTE_MEMBERS
pub fn mute(&mut self, mute: bool) -> &mut Self {
pub fn mute(mut self, mute: bool) -> Self {
self.mute = Some(mute);
self
}
Expand All @@ -61,8 +92,21 @@ impl AddMember {
/// Requires the [Deafen Members] permission.
///
/// [Deafen Members]: crate::model::permissions::Permissions::DEAFEN_MEMBERS
pub fn deafen(&mut self, deafen: bool) -> &mut Self {
pub fn deafen(mut self, deafen: bool) -> Self {
self.deaf = Some(deafen);
self
}

/// Adds a [`User`] to the corresponding guild with a valid OAuth2 access token.
///
/// Returns the created [`Member`] object, or nothing if the user is already a member of the
/// guild.
///
/// # Errors
///
/// Returns [`Error::Http`] if the current user lacks permission, or if invalid values are set.
#[cfg(feature = "http")]
pub async fn execute(self, http: impl AsRef<Http>) -> Result<Option<Member>> {
http.as_ref().add_guild_member(self.guild_id.into(), self.user_id.into(), &self).await
}
}
13 changes: 7 additions & 6 deletions src/builder/bot_auth_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::model::prelude::*;

/// A builder for constructing an invite link with custom OAuth2 scopes.
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct CreateBotAuthParameters {
client_id: Option<ApplicationId>,
scopes: Vec<Scope>,
Expand Down Expand Up @@ -54,7 +55,7 @@ impl CreateBotAuthParameters {
}

/// Specify the client Id of your application.
pub fn client_id<U: Into<ApplicationId>>(&mut self, client_id: U) -> &mut Self {
pub fn client_id<U: Into<ApplicationId>>(mut self, client_id: U) -> Self {
self.client_id = Some(client_id.into());
self
}
Expand All @@ -69,7 +70,7 @@ impl CreateBotAuthParameters {
///
/// [`HttpError::UnsuccessfulRequest`]: crate::http::HttpError::UnsuccessfulRequest
#[cfg(feature = "http")]
pub async fn auto_client_id(&mut self, http: impl AsRef<Http>) -> Result<&mut Self> {
pub async fn auto_client_id(mut self, http: impl AsRef<Http>) -> Result<Self> {
self.client_id = http.as_ref().get_current_application_info().await.map(|v| Some(v.id))?;
Ok(self)
}
Expand All @@ -79,25 +80,25 @@ impl CreateBotAuthParameters {
/// **Note**: This needs to include the [`Bot`] scope.
///
/// [`Bot`]: Scope::Bot
pub fn scopes(&mut self, scopes: &[Scope]) -> &mut Self {
pub fn scopes(mut self, scopes: &[Scope]) -> Self {
self.scopes = scopes.to_vec();
self
}

/// Specify the permissions your application requires.
pub fn permissions(&mut self, permissions: Permissions) -> &mut Self {
pub fn permissions(mut self, permissions: Permissions) -> Self {
self.permissions = permissions;
self
}

/// Specify the Id of the guild to prefill the dropdown picker for the user.
pub fn guild_id<G: Into<GuildId>>(&mut self, guild_id: G) -> &mut Self {
pub fn guild_id<G: Into<GuildId>>(mut self, guild_id: G) -> Self {
self.guild_id = Some(guild_id.into());
self
}

/// Specify whether the user cannot change the guild in the dropdown picker.
pub fn disable_guild_select(&mut self, disable: bool) -> &mut Self {
pub fn disable_guild_select(mut self, disable: bool) -> Self {
self.disable_guild_select = disable;
self
}
Expand Down
Loading