Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add support for sticker packs, guild stickers, and sticker routes (se…
…renity-rs#1395)

This adds the following features:

* Add/modify/delete guild stickers
* Fetch sticker information
* Fetch list of all nitro stickers
* Sending stickers in message (up to 3)

Breaking change as sticker module is moved out of the channel module
and to the root model module.

This also adds an additional `mime` and `mime_guess` dependencies as
Discord requires the mime type on the file part when creating a sticker
via multipart/form-data requests otherwise it will return a 500 Internal
Server Error. This dependency is also used by `reqwest` internally.
  • Loading branch information
drklee3 authored and arqunis committed Sep 30, 2021
1 parent 3021351 commit 18c72cf
Show file tree
Hide file tree
Showing 39 changed files with 1,350 additions and 141 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Expand Up @@ -146,6 +146,12 @@ features = ["std"]
[dependencies.percent-encoding]
version = "2.1"

[dependencies.mime]
version = "0.3.16"

[dependencies.mime_guess]
version = "2.0"

[dev-dependencies.http_crate]
version = "0.2"
package = "http"
Expand Down
2 changes: 1 addition & 1 deletion command_attr/src/structures.rs
Expand Up @@ -352,7 +352,7 @@ impl Permissions {
"MANAGE_NICKNAMES" => 0b0000_1000_0000_0000_0000_0000_0000_0000,
"MANAGE_ROLES" => 0b0001_0000_0000_0000_0000_0000_0000_0000,
"MANAGE_WEBHOOKS" => 0b0010_0000_0000_0000_0000_0000_0000_0000,
"MANAGE_EMOJIS" => 0b0100_0000_0000_0000_0000_0000_0000_0000,
"MANAGE_EMOJIS_AND_STICKERS" => 0b0100_0000_0000_0000_0000_0000_0000_0000,
_ => return None,
}))
}
Expand Down
70 changes: 65 additions & 5 deletions src/builder/create_message.rs
Expand Up @@ -8,16 +8,19 @@ use crate::http::AttachmentType;
use crate::internal::prelude::*;
use crate::json::to_value;
use crate::model::channel::{MessageReference, ReactionType};
use crate::model::id::StickerId;
use crate::utils;

/// A builder to specify the contents of an [`Http::send_message`] request,
/// primarily meant for use through [`ChannelId::send_message`].
///
/// There are two situations where different field requirements are present:
/// There are three situations where different field requirements are present:
///
/// 1. When sending an [`Self::embed`], no other field is required;
/// 2. Otherwise, [`Self::content`] is the only required field that is required to be
/// set.
/// 1. When sending a message without embeds or stickers, [`Self::content`] is
/// the only required field that is required to be set.
/// 2. When sending an [`Self::embed`], no other field is required.
/// 3. When sending stickers with [`Self::sticker_id`] or other sticker methods,
/// no other field is required.
///
/// Note that if you only need to send the content of a message, without
/// specifying other fields, then [`ChannelId::say`] may be a more preferable
Expand Down Expand Up @@ -240,9 +243,66 @@ impl<'a> CreateMessage<'a> {
/// Sets the components of this message.
#[cfg(feature = "unstable_discord_api")]
pub fn set_components(&mut self, components: CreateComponents) -> &mut Self {
self.0.insert("components", Value::Array(components.0));
self.0.insert("components", Value::from(components.0));
self
}

/// Sets a single sticker ID to include in the message.
///
/// **Note**: This will replace all existing stickers. Use
/// [`Self::add_sticker_id()`] to add an additional sticker.
pub fn sticker_id(&mut self, sticker_id: impl Into<StickerId>) -> &mut Self {
self.0.insert("sticker_ids", Value::from(Vec::<Value>::new()));
self.add_sticker_id(sticker_id)
}

/// Add a sticker ID for the message.
///
/// **Note**: There can be a maximum of 3 stickers in a message.
///
/// **Note**: This will keep all existing stickers. Use
/// [`Self::set_sticker_ids()`] to replace existing stickers.
pub fn add_sticker_id(&mut self, sticker_id: impl Into<StickerId>) -> &mut Self {
let sticker_ids =
self.0.entry("sticker_ids").or_insert_with(|| Value::from(Vec::<Value>::new()));
let sticker_ids_array = sticker_ids.as_array_mut().expect("Sticker_ids must be an array");

sticker_ids_array.push(Value::from(sticker_id.into().0));

self
}

/// Add multiple sticker IDs for the message.
///
/// **Note**: There can be a maximum of 3 stickers in a message.
///
/// **Note**: This will keep all existing stickers. Use
/// [`Self::set_sticker_ids()`] to replace existing stickers.
pub fn add_sticker_ids<T: Into<StickerId>, It: IntoIterator<Item = T>>(
&mut self,
sticker_ids: It,
) -> &mut Self {
for sticker_id in sticker_ids.into_iter() {
self.add_sticker_id(sticker_id);
}

self
}

/// Sets a list of sticker IDs to include in the message.
///
/// **Note**: There can be a maximum of 3 stickers in a message.
///
/// **Note**: This will replace all existing stickers. Use
/// [`Self::add_sticker_id()`] or [`Self::add_sticker_ids()`] to keep
/// existing stickers.
pub fn set_sticker_ids<T: Into<StickerId>, It: IntoIterator<Item = T>>(
&mut self,
sticker_ids: It,
) -> &mut Self {
self.0.insert("sticker_ids", Value::from(Vec::<Value>::new()));
self.add_sticker_ids(sticker_ids)
}
}

impl<'a> Default for CreateMessage<'a> {
Expand Down
53 changes: 53 additions & 0 deletions src/builder/create_sticker.rs
@@ -0,0 +1,53 @@
use std::collections::HashMap;

use crate::http::AttachmentType;
use crate::internal::prelude::*;

/// A builder to create or edit a [`Sticker`] for use via a number of model methods.
///
/// These are:
///
/// - [`PartialGuild::create_sticker`]
/// - [`Guild::create_sticker`]
/// - [`GuildId::create_sticker`]
///
/// [`Sticker`]: crate::model::sticker::Sticker
/// [`PartialGuild::create_sticker`]: crate::model::guild::PartialGuild::create_sticker
/// [`Guild::create_sticker`]: crate::model::guild::Guild::create_sticker
/// [`GuildId::create_sticker`]: crate::model::id::GuildId::create_sticker
#[derive(Clone, Debug, Default)]
pub struct CreateSticker<'a>(pub HashMap<&'static str, Value>, pub Option<AttachmentType<'a>>);

impl<'a> CreateSticker<'a> {
/// The name of the sticker to set.
///
/// **Note**: Must be between 2 and 30 characters long.
pub fn name<S: ToString>(&mut self, name: S) -> &mut Self {
self.0.insert("name", Value::from(name.to_string()));
self
}

/// The description of the sticker.
///
/// **Note**: If not empty, must be between 2 and 100 characters long.
pub fn description<S: ToString>(&mut self, description: S) -> &mut Self {
self.0.insert("description", Value::from(description.to_string()));
self
}

/// The Discord name of a unicode emoji representing the sticker's expression.
///
/// **Note**: Must be between 2 and 200 characters long.
pub fn tags<S: ToString>(&mut self, tags: S) -> &mut Self {
self.0.insert("tags", Value::from(tags.to_string()));
self
}

/// The sticker file.
///
/// **Note**: Must be a PNG, APNG, or Lottie JSON file, max 500 KB.
pub fn file<T: Into<AttachmentType<'a>>>(&mut self, file: T) -> &mut Self {
self.1 = Some(file.into());
self
}
}
46 changes: 46 additions & 0 deletions src/builder/edit_sticker.rs
@@ -0,0 +1,46 @@
use std::collections::HashMap;

use crate::internal::prelude::*;

/// A builder to create or edit a [`Sticker`] for use via a number of model methods.
///
/// These are:
///
/// - [`Guild::edit_sticker`]
/// - [`PartialGuild::edit_sticker`]
/// - [`GuildId::edit_sticker`]
/// - [`Sticker::edit`]
///
/// [`Sticker`]: crate::model::sticker::Sticker
/// [`PartialGuild::edit_sticker`]: crate::model::guild::PartialGuild::edit_sticker
/// [`Guild::edit_sticker`]: crate::model::guild::Guild::edit_sticker
/// [`GuildId::edit_sticker`]: crate::model::id::GuildId::edit_sticker
/// [`Sticker::edit`]: crate::model::sticker::Sticker::edit
#[derive(Clone, Debug, Default)]
pub struct EditSticker(pub HashMap<&'static str, Value>);

impl EditSticker {
/// The name of the sticker to set.
///
/// **Note**: Must be between 2 and 30 characters long.
pub fn name<S: ToString>(&mut self, name: S) -> &mut Self {
self.0.insert("name", Value::from(name.to_string()));
self
}

/// The description of the sticker.
///
/// **Note**: If not empty, must be between 2 and 100 characters long.
pub fn description<S: ToString>(&mut self, description: S) -> &mut Self {
self.0.insert("description", Value::from(description.to_string()));
self
}

/// The Discord name of a unicode emoji representing the sticker's expression.
///
/// **Note**: Must be between 2 and 200 characters long.
pub fn tags<S: ToString>(&mut self, tags: S) -> &mut Self {
self.0.insert("tags", Value::from(tags.to_string()));
self
}
}
4 changes: 4 additions & 0 deletions src/builder/mod.rs
Expand Up @@ -29,6 +29,7 @@ mod create_interaction_response_followup;
mod create_invite;
mod create_message;
mod create_stage_instance;
mod create_sticker;
mod create_thread;
mod edit_channel;
mod edit_guild;
Expand All @@ -42,6 +43,7 @@ mod edit_message;
mod edit_profile;
mod edit_role;
mod edit_stage_instance;
mod edit_sticker;
mod edit_thread;
mod edit_voice_state;
mod edit_webhook_message;
Expand All @@ -57,6 +59,7 @@ pub use self::{
create_invite::CreateInvite,
create_message::CreateMessage,
create_stage_instance::CreateStageInstance,
create_sticker::CreateSticker,
create_thread::CreateThread,
edit_channel::EditChannel,
edit_guild::EditGuild,
Expand All @@ -67,6 +70,7 @@ pub use self::{
edit_profile::EditProfile,
edit_role::EditRole,
edit_stage_instance::EditStageInstance,
edit_sticker::EditSticker,
edit_thread::EditThread,
edit_voice_state::EditVoiceState,
edit_webhook_message::EditWebhookMessage,
Expand Down
3 changes: 2 additions & 1 deletion src/cache/mod.rs
Expand Up @@ -1091,7 +1091,7 @@ mod test {
application: None,
message_reference: None,
flags: None,
stickers: vec![],
sticker_items: vec![],
referenced_message: None,
#[cfg(feature = "unstable_discord_api")]
interaction: None,
Expand Down Expand Up @@ -1214,6 +1214,7 @@ mod test {
widget_channel_id: None,
stage_instances: vec![],
threads: vec![],
stickers: HashMap::new(),
},
}
};
Expand Down
11 changes: 6 additions & 5 deletions src/client/bridge/gateway/intents.rs
Expand Up @@ -86,7 +86,8 @@ __impl_bitflags! {
/// Enables following gateway event:
///
/// - GUILD_EMOJIS_UPDATE
GUILD_EMOJIS = 1 << 3;
/// - GUILD_STICKERS_UPDATE
GUILD_EMOJIS_AND_STICKERS = 1 << 3;
/// Enables following gateway event:
///
/// - GUILD_INTEGRATIONS_UPDATE
Expand Down Expand Up @@ -228,11 +229,11 @@ impl GatewayIntents {
}

/// Shorthand for checking that the set of intents contains the
/// [GUILD_EMOJIS] intent.
/// [GUILD_EMOJIS_AND_STICKERS] intent.
///
/// [GUILD_EMOJIS]: Self::GUILD_EMOJIS
pub fn guild_emojis(self) -> bool {
self.contains(Self::GUILD_EMOJIS)
/// [GUILD_EMOJIS_AND_STICKERS]: Self::GUILD_EMOJIS_AND_STICKERS
pub fn guild_emojis_and_stickers(self) -> bool {
self.contains(Self::GUILD_EMOJIS_AND_STICKERS)
}

/// Shorthand for checking that the set of intents contains the
Expand Down
11 changes: 11 additions & 0 deletions src/client/dispatch.rs
Expand Up @@ -119,6 +119,9 @@ impl DispatchEvent {
Self::Model(Event::GuildRoleUpdate(ref mut event)) => {
update(cache_and_http, event).await;
},
Self::Model(Event::GuildStickersUpdate(ref mut event)) => {
update(cache_and_http, event).await;
},
Self::Model(Event::GuildUnavailable(ref mut event)) => {
update(cache_and_http, event).await;
},
Expand Down Expand Up @@ -572,6 +575,14 @@ async fn handle_event(
}}
});
},
DispatchEvent::Model(Event::GuildStickersUpdate(mut event)) => {
update(&cache_and_http, &mut event).await;
let event_handler = Arc::clone(event_handler);

tokio::spawn(async move {
event_handler.guild_stickers_update(context, event.guild_id, event.stickers).await;
});
},
DispatchEvent::Model(Event::GuildUnavailable(mut event)) => {
update(&cache_and_http, &mut event).await;
let event_handler = Arc::clone(event_handler);
Expand Down
11 changes: 11 additions & 0 deletions src/client/event_handler.rs
Expand Up @@ -239,6 +239,17 @@ pub trait EventHandler: Send + Sync {
#[cfg(not(feature = "cache"))]
async fn guild_role_update(&self, _ctx: Context, _guild_id: GuildId, _new_data: Role) {}

/// Dispatched when the stickers are updated.
///
/// Provides the guild's id and the new state of the stickers in the guild.
async fn guild_stickers_update(
&self,
_ctx: Context,
_guild_id: GuildId,
_current_state: HashMap<StickerId, Sticker>,
) {
}

/// Dispatched when a guild became unavailable.
///
/// Provides the guild's id.
Expand Down
3 changes: 3 additions & 0 deletions src/constants.rs
Expand Up @@ -6,6 +6,9 @@ pub const EMBED_MAX_LENGTH: usize = 6000;
/// The maximum number of embeds in a message.
pub const EMBED_MAX_COUNT: usize = 10;

/// The maximum number of stickers in a message.
pub const STICKER_MAX_COUNT: usize = 3;

/// The gateway version used by the library. The gateway URI is retrieved via
/// the REST API.
pub const GATEWAY_VERSION: u8 = 9;
Expand Down

0 comments on commit 18c72cf

Please sign in to comment.