diff options
| author | Austin Hellyer <[email protected]> | 2016-11-07 11:43:15 -0800 |
|---|---|---|
| committer | Austin Hellyer <[email protected]> | 2016-11-07 11:43:15 -0800 |
| commit | a114a55efb5b08f9e5f289203db2dfd4db82852a (patch) | |
| tree | 8522e8eb3e77534ecf5c8e0208746b587eae887b /src | |
| parent | Add Attachment::download{,to_directory} (diff) | |
| download | serenity-a114a55efb5b08f9e5f289203db2dfd4db82852a.tar.xz serenity-a114a55efb5b08f9e5f289203db2dfd4db82852a.zip | |
Add webhook support
Diffstat (limited to 'src')
| -rw-r--r-- | src/builder.rs | 92 | ||||
| -rw-r--r-- | src/client/dispatch.rs | 10 | ||||
| -rw-r--r-- | src/client/event_store.rs | 1 | ||||
| -rw-r--r-- | src/client/http/mod.rs | 342 | ||||
| -rw-r--r-- | src/client/http/ratelimiting.rs | 4 | ||||
| -rw-r--r-- | src/client/mod.rs | 10 | ||||
| -rw-r--r-- | src/model/channel.rs | 23 | ||||
| -rw-r--r-- | src/model/gateway.rs | 16 | ||||
| -rw-r--r-- | src/model/guild.rs | 20 | ||||
| -rw-r--r-- | src/model/id.rs | 29 | ||||
| -rw-r--r-- | src/model/mod.rs | 4 | ||||
| -rw-r--r-- | src/model/webhook.rs | 168 |
12 files changed, 718 insertions, 1 deletions
diff --git a/src/builder.rs b/src/builder.rs index f7c9f49..05ba3ac 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -20,6 +20,46 @@ use ::model::{ permissions, }; +/// 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()) + } +} + /// 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 @@ -88,6 +128,58 @@ impl Default for CreateInvite { } } +/// 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)) + } +} + /// A builer to create or edit a [`Role`] for use via a number of model and /// context methods. /// diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index 92f3624..a629f33 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -665,6 +665,16 @@ pub fn dispatch(event: Result<Event>, }); } }, + Ok(Event::WebhookUpdate(event)) => { + if let Some(ref handler) = handler!(on_webhook_update, event_store) { + let context = context(None, conn, login_type); + let handler = handler.clone(); + + thread::spawn(move || { + (handler)(context, event.guild_id, event.channel_id); + }); + } + }, Err(_why) => {}, } } diff --git a/src/client/event_store.rs b/src/client/event_store.rs index 0ba5301..57f8dee 100644 --- a/src/client/event_store.rs +++ b/src/client/event_store.rs @@ -72,4 +72,5 @@ pub struct EventStore { pub on_user_settings_update: Option<Arc<Fn(Context, UserSettings, UserSettings) + Send + Sync + 'static>>, pub on_voice_state_update: Option<Arc<Fn(Context, VoiceStateUpdateEvent) + Send + Sync + 'static>>, pub on_voice_server_update: Option<Arc<Fn(Context, VoiceServerUpdateEvent) + Send + Sync + 'static>>, + pub on_webhook_update: Option<Arc<Fn(Context, GuildId, ChannelId) + Send + Sync + 'static>>, } diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index 9ef9bac..a16e6b0 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -183,6 +183,46 @@ pub fn create_role(guild_id: u64) -> Result<Role> { Role::decode(try!(serde_json::from_reader(response))) } +/// Creates a webhook for the given [channel][`PublicChannel`]'s Id, passing in +/// the given data. +/// +/// This method requires authentication. +/// +/// The Value is a map with the values of: +/// +/// - **avatar**: base64-encoded 128x128 image for the webhook's default avatar +/// (_optional_); +/// - **name**: the name of the webhook, limited to between 2 and 100 characters +/// long. +/// +/// # Examples +/// +/// Creating a webhook named `test`: +/// +/// ```rust,ignore +/// extern crate serde_json; +/// extern crate serenity; +/// +/// use serde_json::builder::ObjectBuilder; +/// use serenity::client::http; +/// +/// let channel_id = 81384788765712384; +/// let map = ObjectBuilder::new().insert("name", "test").build(); +/// +/// let webhook = http::create_webhook(channel_id, map).expect("err creating"); +/// ``` +/// +/// [`PublicChannel`]: ../../model/struct.PublicChannel.html +pub fn create_webhook(channel_id: u64, map: Value) -> Result<Webhook> { + let body = try!(serde_json::to_string(&map)); + let response = request!(Route::ChannelsIdWebhooks(channel_id), + post(body), + "/channels/{}/webhooks", + channel_id); + + Webhook::decode(try!(serde_json::from_reader(response))) +} + pub fn delete_channel(channel_id: u64) -> Result<Channel> { let response = request!(Route::ChannelsId(channel_id), delete, @@ -275,6 +315,56 @@ pub fn delete_role(guild_id: u64, role_id: u64) -> Result<()> { role_id)) } +/// Deletes a [`Webhook`] given its Id. +/// +/// This method requires authentication, whereas [`delete_webhook_with_token`] +/// does not. +/// +/// # Examples +/// +/// Delete a webhook given its Id: +/// +/// ```rust,no_run +/// use serenity::client::{Client, http}; +/// use std::env; +/// +/// // Due to the `delete_webhook` function requiring you to authenticate, you +/// // must have initialized a client first. +/// let client = Client::login_user(&env::var("DISCORD_TOKEN").unwrap()); +/// +/// http::delete_webhook(245037420704169985).expect("err deleting webhook"); +/// ``` +/// +/// [`Webhook`]: ../../model/struct.Webhook.html +/// [`delete_webhook_with_token`]: fn.delete_webhook_with_token.html +pub fn delete_webhook(webhook_id: u64) -> Result<()> { + verify(204, request!(Route::WebhooksId, delete, "/webhooks/{}", webhook_id)) +} + +/// Deletes a [`Webhook`] given its Id and unique token. +/// +/// This method does _not_ require authentication. +/// +/// # Examples +/// +/// Delete a webhook given its Id and unique token: +/// +/// ```rust,no_run +/// use serenity::client::http; +/// +/// let id = 245037420704169985; +/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; +/// +/// http::delete_webhook_with_token(id, token).expect("err deleting webhook"); +/// +/// [`Webhook`]: ../../model/struct.Webhook.html +pub fn delete_webhook_with_token(webhook_id: u64, token: &str) -> Result<()> { + let client = HyperClient::new(); + verify(204, try!(retry(|| client + .delete(&format!(api!("/webhooks/{}/{}"), webhook_id, token))) + .map_err(Error::Hyper))) +} + pub fn edit_channel(channel_id: u64, map: Value) -> Result<PublicChannel> { let body = try!(serde_json::to_string(&map)); @@ -361,6 +451,155 @@ pub fn edit_role(guild_id: u64, role_id: u64, map: Value) Role::decode(try!(serde_json::from_reader(response))) } +/// Edits a the webhook with the given data. +/// +/// The Value is a map with optional values of: +/// +/// - **avatar**: base64-encoded 128x128 image for the webhook's default avatar +/// (_optional_); +/// - **name**: the name of the webhook, limited to between 2 and 100 characters +/// long. +/// +/// Note that, unlike with [`create_webhook`], _all_ values are optional. +/// +/// This method requires authentication, whereas [`edit_webhook_with_token`] +/// does not. +/// +/// # Examples +/// +/// Edit the image of a webhook given its Id and unique token: +/// +/// ```rust,ignore +/// extern crate serde_json; +/// extern crate serenity; +/// +/// use serde_json::builder::ObjectBuilder; +/// use serenity::client::http; +/// +/// let id = 245037420704169985; +/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; +/// let image = serenity::utils::read_image("./webhook_img.png") +/// .expect("err reading image"); +/// let map = ObjectBuilder::new().insert("avatar", image).build(); +/// +/// let edited = http::edit_webhook_with_token(id, token, map) +/// .expect("err editing webhook"); +/// ``` +/// +/// [`create_webhook`]: fn.create_webhook.html +/// [`edit_webhook_with_token`]: fn.edit_webhook_with_token.html +// The tests are ignored, rather than no_run'd, due to rustdoc tests with +// external crates being incredibly messy and misleading in the end user's view. +pub fn edit_webhook(webhook_id: u64, map: Value) -> Result<Webhook> { + let body = try!(serde_json::to_string(&map)); + let response = request!(Route::WebhooksId, + patch(body), + "/webhooks/{}", + webhook_id); + + Webhook::decode(try!(serde_json::from_reader(response))) +} + +/// Edits the webhook with the given data. +/// +/// Refer to the documentation for [`edit_webhook`] for more information. +/// +/// This method does _not_ require authentication. +/// +/// # Examples +/// +/// Edit the name of a webhook given its Id and unique token: +/// +/// ```rust,ignore +/// extern crate serde_json; +/// extern crate serenity; +/// +/// use serde_json::builder::ObjectBuilder; +/// use serenity::client::http; +/// +/// let id = 245037420704169985; +/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; +/// let map = ObjectBuilder::new().insert("name", "new name").build(); +/// +/// let edited = http::edit_webhook_with_token(id, token, map) +/// .expect("err editing webhook"); +/// ``` +/// +/// [`edit_webhook`]: fn.edit_webhook.html +pub fn edit_webhook_with_token(webhook_id: u64, token: &str, map: Value) + -> Result<Webhook> { + let body = try!(serde_json::to_string(&map)); + let client = HyperClient::new(); + let response = try!(retry(|| client + .patch(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) + .body(&body)) + .map_err(Error::Hyper)); + + Webhook::decode(try!(serde_json::from_reader(response))) +} + +/// Executes a webhook, posting a [`Message`] in the webhook's associated +/// [`Channel`]. +/// +/// This method does _not_ require authentication. +/// +/// Pass `true` to `wait` to wait for server confirmation of the message sending +/// before receiving a response. From the [Discord docs]: +/// +/// > waits for server confirmation of message send before response, and returns +/// > the created message body (defaults to false; when false a message that is +/// > not saved does not return an error) +/// +/// The map can _optionally_ contain the following data: +/// +/// - **avatar_url**: Override the default avatar of the webhook with a URL. +/// - **tts**: Whether this is a text-to-speech message (defaults to `false`). +/// - **username**: Override the default username of the webhook. +/// +/// Additionally, _at least one_ of the following must be given: +/// +/// - **content**: The content of the message. +/// - **embeds**: An array of rich embeds. +/// +/// **Note**: For embed objects, all fields are registered by Discord except for +/// `height`, `provider`, `proxy_url`, `type` (it will always be `rich`), +/// `video`, and `width`. The rest will be determined by Discord. +/// +/// # Examples +/// +/// Sending a webhook with message content of `test`: +/// +/// ```rust,ignore +/// extern crate serde_json; +/// extern crate serenity; +/// +/// use serde_json::builder::ObjectBuilder; +/// use serenity::client::http; +/// +/// let id = 245037420704169985; +/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; +/// let map = ObjectBuilder::new().insert("content", "test").build(); +/// +/// let message = match http::execute_webhook(id, token, map) { +/// Ok(message) => message, +/// Err(why) => { +/// println!("Error executing webhook: {:?}", why); +/// +/// return; +/// }, +/// }; +pub fn execute_webhook(webhook_id: u64, token: &str, map: Value) + -> Result<Message> { + let body = try!(serde_json::to_string(&map)); + let client = HyperClient::new(); + let response = try!(retry(|| client + .patch(&format!(api!("/webhooks/{}/{}"), webhook_id, token)) + .body(&body)) + .map_err(Error::Hyper)); + + Message::decode(try!(serde_json::from_reader(response))) +} + pub fn get_application_info() -> Result<CurrentApplicationInfo> { let response = request!(Route::None, get, "/oauth2/applications/@me"); @@ -400,6 +639,33 @@ pub fn get_channel_invites(channel_id: u64) RichInvite::decode) } +/// Retrieves the webhooks for the given [channel][`PublicChannel`]'s Id. +/// +/// This method requires authentication. +/// +/// # Examples +/// +/// Retrieve all of the webhooks owned by a channel: +/// +/// ```rust,no_run +/// use serenity::client::http; +/// +/// let channel_id = 81384788765712384; +/// +/// let webhooks = http::get_channel_webhooks(channel_id) +/// .expect("err getting channel webhooks"); +/// ``` +/// +/// [`PublicChannel`]: ../../model/struct.PublicChannel.html +pub fn get_channel_webhooks(channel_id: u64) -> Result<Vec<Webhook>> { + let response = request!(Route::ChannelsIdWebhooks(channel_id), + get, + "/channels/{}/webhooks", + channel_id); + + decode_array(try!(serde_json::from_reader(response)), Webhook::decode) +} + pub fn get_channel(channel_id: u64) -> Result<Channel> { let response = request!(Route::ChannelsId(channel_id), get, @@ -490,6 +756,33 @@ pub fn get_guild_prune_count(guild_id: u64, map: Value) GuildPrune::decode(try!(serde_json::from_reader(response))) } +/// Retrieves the webhooks for the given [guild][`Guild`]'s Id. +/// +/// This method requires authentication. +/// +/// # Examples +/// +/// Retrieve all of the webhooks owned by a guild: +/// +/// ```rust,no_run +/// use serenity::client::http; +/// +/// let guild_id = 81384788765712384; +/// +/// let webhooks = http::get_guild_webhooks(guild_id) +/// .expect("err getting guild webhooks"); +/// ``` +/// +/// [`Guild`]: ../../model/struct.Guild.html +pub fn get_guild_webhooks(guild_id: u64) -> Result<Vec<Webhook>> { + let response = request!(Route::GuildsIdWebhooks(guild_id), + get, + "/guilds/{}/webhooks", + guild_id); + + decode_array(try!(serde_json::from_reader(response)), Webhook::decode) +} + pub fn get_guilds() -> Result<Vec<GuildInfo>> { let response = request!(Route::UsersMeGuilds, get, @@ -584,6 +877,55 @@ pub fn get_voice_regions() -> Result<Vec<VoiceRegion>> { decode_array(try!(serde_json::from_reader(response)), VoiceRegion::decode) } +/// Retrieves a webhook given its Id. +/// +/// This method requires authentication, whereas [`get_webhook_with_token`] does +/// not. +/// +/// # Examples +/// +/// Retrieve a webhook by Id: +/// +/// ```rust,no_run +/// use serenity::client::http; +/// +/// let id = 245037420704169985; +/// let webhook = http::get_webhook(id).expect("err getting webhook"); +/// ``` +/// +/// [`get_webhook_with_token`]: fn.get_webhook_with_token.html +pub fn get_webhook(webhook_id: u64) -> Result<Webhook> { + let response = request!(Route::WebhooksId, get, "/webhooks/{}", webhook_id); + + Webhook::decode(try!(serde_json::from_reader(response))) +} + +/// Retrieves a webhook given its Id and unique token. +/// +/// This method does _not_ require authentication. +/// +/// # Examples +/// +/// Retrieve a webhook by Id and its unique token: +/// +/// ```rust,no_run +/// use serenity::client::http; +/// +/// let id = 245037420704169985; +/// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; +/// +/// let webhook = http::get_webhook_with_token(id, token) +/// .expect("err getting webhook"); +/// ``` +pub fn get_webhook_with_token(webhook_id: u64, token: &str) -> Result<Webhook> { + let client = HyperClient::new(); + let response = try!(retry(|| client + .get(&format!(api!("/webhooks/{}/{}"), webhook_id, token))) + .map_err(Error::Hyper)); + + Webhook::decode(try!(serde_json::from_reader(response))) +} + pub fn kick_member(guild_id: u64, user_id: u64) -> Result<()> { verify(204, request!(Route::GuildsIdMembersId(guild_id), delete, diff --git a/src/client/http/ratelimiting.rs b/src/client/http/ratelimiting.rs index 6e3066d..3f4f885 100644 --- a/src/client/http/ratelimiting.rs +++ b/src/client/http/ratelimiting.rs @@ -67,6 +67,7 @@ pub enum Route { ChannelsIdPins(u64), ChannelsIdPinsMessageId(u64), ChannelsIdTyping(u64), + ChannelsIdWebhooks(u64), Gateway, GatewayBot, Global, @@ -88,6 +89,7 @@ pub enum Route { GuildsIdRegions(u64), GuildsIdRoles(u64), GuildsIdRolesId(u64), + GuildsIdWebhooks(u64), InvitesCode, Users, UsersId, @@ -97,6 +99,8 @@ pub enum Route { UsersMeGuilds, UsersMeGuildsId, VoiceRegions, + WebhooksId, + WebhooksIdToken, None, } diff --git a/src/client/mod.rs b/src/client/mod.rs index 95af69f..e010ebf 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -864,6 +864,16 @@ impl Client { .on_voice_server_update = Some(Arc::new(handler)); } + /// Attaches a handler for when a [`WebhookUpdate`] is received. + /// + /// [`WebhookUpdate`]: ../model/enum.Event.html#variant.WebhookUpdate + pub fn on_webhook_update<F>(&mut self, handler: F) + where F: Fn(Context, GuildId, ChannelId) + Send + Sync + 'static { + self.event_store.lock() + .unwrap() + .on_webhook_update = Some(Arc::new(handler)); + } + // Shard data layout is: // 0: first shard number to initialize // 1: shard number to initialize up to and including diff --git a/src/model/channel.rs b/src/model/channel.rs index 014442a..facb67a 100644 --- a/src/model/channel.rs +++ b/src/model/channel.rs @@ -16,7 +16,7 @@ use super::utils::{ }; use super::*; use super::utils; -use ::builder::{CreateInvite, EditChannel}; +use ::builder::{CreateEmbed, CreateInvite, EditChannel}; use ::client::{STATE, http}; use ::prelude_internal::*; use ::utils::decode_array; @@ -251,6 +251,18 @@ impl fmt::Display for Channel { } } +impl Embed { + /// Creates a fake Embed, giving back a `serde_json` map. + /// + /// This should only be useful in conjunction with [`Webhook::execute`]. + /// + /// [`Webhook::execute`]: struct.Webhook.html + #[inline(always)] + pub fn fake<F>(f: F) -> Value where F: FnOnce(CreateEmbed) -> CreateEmbed { + f(CreateEmbed::default()).0.build() + } +} + impl Group { /// Adds the given user to the group. If the user is already in the group, /// then nothing is done. @@ -764,6 +776,15 @@ impl PublicChannel { http::send_message(self.id.0, map) } + + /// Retrieves the channel's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + pub fn webhooks(&self) -> Result<Vec<Webhook>> { + http::get_channel_webhooks(self.id.0) + } } impl fmt::Display for PublicChannel { diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 60c9566..5748302 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -302,6 +302,12 @@ pub struct VoiceStateUpdateEvent { pub voice_state: VoiceState, } +#[derive(Clone, Debug)] +pub struct WebhookUpdateEvent { + pub channel_id: ChannelId, + pub guild_id: GuildId, +} + #[derive(Debug, Clone)] pub enum GatewayEvent { Dispatch(u64, Event), @@ -483,6 +489,11 @@ pub enum Event { VoiceStateUpdate(VoiceStateUpdateEvent), /// Voice server information is available VoiceServerUpdate(VoiceServerUpdateEvent), + /// A webhook for a [channel][`PublicChannel`] was updated in a [`Guild`]. + /// + /// [`Guild`]: struct.Guild.html + /// [`PublicChannel`]: struct.PublicChannel.html + WebhookUpdate(WebhookUpdateEvent), /// An event type not covered by the above Unknown(UnknownEvent), } @@ -748,6 +759,11 @@ impl Event { guild_id: try!(opt(&mut value, "guild_id", GuildId::decode)), voice_state: try!(VoiceState::decode(Value::Object(value))), })) + } else if kind == "WEBHOOKS_UPDATE" { + Ok(Event::WebhookUpdate(WebhookUpdateEvent { + channel_id: try!(remove(&mut value, "channel_id").and_then(ChannelId::decode)), + guild_id: try!(remove(&mut value, "guild_id").and_then(GuildId::decode)), + })) } else { Ok(Event::Unknown(UnknownEvent { kind: kind, diff --git a/src/model/guild.rs b/src/model/guild.rs index a72a187..e8aff56 100644 --- a/src/model/guild.rs +++ b/src/model/guild.rs @@ -125,6 +125,16 @@ impl Guild { self.icon.as_ref().map(|icon| format!(cdn_concat!("/icons/{}/{}.jpg"), self.id, icon)) } + + /// Retrieves the guild's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn webhooks(&self) -> Result<Vec<Webhook>> { + http::get_guild_webhooks(self.id.0) + } } impl LiveGuild { @@ -674,6 +684,16 @@ impl LiveGuild { http::remove_ban(self.id.0, user.into().0) } + + /// Retrieves the guild's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + #[inline] + pub fn webhooks(&self) -> Result<Vec<Webhook>> { + http::get_guild_webhooks(self.id.0) + } } impl Member { diff --git a/src/model/id.rs b/src/model/id.rs index 967b18c..faeccd3 100644 --- a/src/model/id.rs +++ b/src/model/id.rs @@ -28,6 +28,15 @@ impl ChannelId { prefix: "<#", } } + + /// Retrieves the channel's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + pub fn webhooks(&self) -> Result<Vec<Webhook>> { + http::get_channel_webhooks(self.0) + } } impl From<Channel> for ChannelId { @@ -81,6 +90,15 @@ impl GuildId { prefix: "<#", } } + + /// Retrieves the guild's webhooks. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + pub fn webhooks(&self) -> Result<Vec<Webhook>> { + http::get_guild_webhooks(self.0) + } } impl From<Guild> for GuildId { @@ -181,3 +199,14 @@ impl UserId { } } } + +impl WebhookId { + /// Retrieves the webhook by the Id. + /// + /// **Note**: Requires the [Manage Webhooks] permission. + /// + /// [Manage Webhooks]: permissions/constant.MANAGE_WEBHOOKS.html + pub fn webhooks(&self) -> Result<Webhook> { + http::get_webhook(self.0) + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 1e94d99..81750a0 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -11,6 +11,7 @@ mod invite; mod misc; mod user; mod voice; +mod webhook; pub use self::channel::*; pub use self::gateway::*; @@ -21,6 +22,7 @@ pub use self::misc::*; pub use self::permissions::Permissions; pub use self::user::*; pub use self::voice::*; +pub use self::webhook::*; use self::utils::*; use std::collections::HashMap; @@ -87,6 +89,8 @@ id! { RoleId; /// An identifier for a User UserId; + /// An identifier for a [`Webhook`](struct.Webhook.html). + WebhookId; } /// A container for any channel. diff --git a/src/model/webhook.rs b/src/model/webhook.rs new file mode 100644 index 0000000..70acd68 --- /dev/null +++ b/src/model/webhook.rs @@ -0,0 +1,168 @@ +use serde_json::builder::ObjectBuilder; +use std::mem; +use super::{Message, Webhook}; +use ::builder::ExecuteWebhook; +use ::client::http; +use ::prelude_internal::*; + +impl Webhook { + /// Deletes the webhook. + /// + /// As this calls the [`http::delete_webhook_with_token`] function, + /// authentication is not required. + /// + /// [`http::delete_webhook_with_token`]: ../client/http/fn.delete_webhook_with_token.html + pub fn delete(&self) -> Result<()> { + http::delete_webhook_with_token(self.id.0, &self.token) + } + + /// + /// Edits the webhook in-place. All fields are optional. + /// + /// To nullify the avatar, pass `Some("")`. Otherwise, passing `None` will + /// not modify the avatar. + /// + /// Refer to [`http::edit_webhook`] for restrictions on editing webhooks. + /// + /// As this calls the [`http::edit_webhook_with_token`] function, + /// authentication is not required. + /// + /// # Examples + /// + /// Editing a webhook's name: + /// + /// ```rust,no_run + /// use serenity::client::http; + /// + /// let id = 245037420704169985; + /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; + /// + /// let mut webhook = http::get_webhook_with_token(id, token) + /// .expect("valid webhook"); + /// + /// let _ = webhook.edit(Some("new name"), None).expect("err editing"); + /// ``` + /// + /// Setting a webhook's avatar: + /// + /// ```rust,no_run + /// use serenity::client::http; + /// + /// let id = 245037420704169985; + /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; + /// + /// let mut webhook = http::get_webhook_with_token(id, token) + /// .expect("valid webhook"); + /// + /// let image = serenity::utils::read_image("./webhook_img.png") + /// .expect("err reading image"); + /// + /// let _ = webhook.edit(None, Some(&image)).expect("err editing"); + /// ``` + /// + /// [`http::edit_webhook`]: ../client/http/fn.edit_webhook.html + /// [`http::edit_webhook_with_token`]: ../client/http/fn.edit_webhook_with_token.html + pub fn edit(&mut self, name: Option<&str>, avatar: Option<&str>) + -> Result<()> { + if name.is_none() && avatar.is_none() { + return Ok(()); + } + + let mut map = ObjectBuilder::new(); + + if let Some(avatar) = avatar { + map = map.insert("avatar", if avatar.len() == 0 { + Value::Null + } else { + Value::String(avatar.to_owned()) + }); + } + + if let Some(name) = name { + map = map.insert("name", name); + } + + let map = map.build(); + + match http::edit_webhook_with_token(self.id.0, &self.token, map) { + Ok(replacement) => { + mem::replace(self, replacement); + + Ok(()) + }, + Err(why) => Err(why), + } + } + + /// Executes a webhook with the fields set via the given builder. + /// + /// The builder provides a method of setting only the fields you need, + /// without needing to pass a long set of arguments. + /// + /// # Examples + /// + /// Execute a webhook with message content of `test`: + /// + /// ```rust,no_run + /// use serenity::client::http; + /// + /// let id = 245037420704169985; + /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; + /// + /// let mut webhook = http::get_webhook_with_token(id, token) + /// .expect("valid webhook"); + /// + /// let _ = webhook.execute(|w| w.content("test")).expect("err executing"); + /// ``` + /// + /// Execute a webhook with message content of `test`, overriding the + /// username to `serenity`, and sending an embed: + /// + /// ```rust,no_run + /// use serenity::client::http; + /// use serenity::model::Embed; + /// + /// let id = 245037420704169985; + /// let token = "ig5AO-wdVWpCBtUUMxmgsWryqgsW3DChbKYOINftJ4DCrUbnkedoYZD0VOH1QLr-S3sV"; + /// + /// let mut webhook = http::get_webhook_with_token(id, token) + /// .expect("valid webhook"); + /// + /// let embed = Embed::fake(|e| e + /// .title("Rust's website") + /// .description("Rust is a systems programming language that runs + /// blazingly fast, prevents segfaults, and guarantees + /// thread safety.") + /// .url("https://rust-lang.org")); + /// + /// let _ = webhook.execute(|w| w + /// .content("test") + /// .username("serenity") + /// .embeds(vec![embed])) + /// .expect("err executing"); + /// ``` + pub fn execute<F>(&self, f: F) -> Result<Message> + where F: FnOnce(ExecuteWebhook) -> ExecuteWebhook { + let map = f(ExecuteWebhook::default()).0.build(); + + http::execute_webhook(self.id.0, &self.token, map) + } + + /// Retrieves the latest information about the webhook, editing the + /// webhook in-place. + /// + /// As this calls the [`http::get_webhook_with_token`] function, + /// authentication is not required. + /// + /// [`http::get_webhook_with_token`]: ../client/http/fn.get_webhook_with_token.html + pub fn refresh(&mut self) -> Result<()> { + match http::get_webhook_with_token(self.id.0, &self.token) { + Ok(replacement) => { + mem::replace(self, replacement); + + Ok(()) + }, + Err(why) => Err(why), + } + } +} |