aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAustin Hellyer <[email protected]>2016-11-07 11:43:15 -0800
committerAustin Hellyer <[email protected]>2016-11-07 11:43:15 -0800
commita114a55efb5b08f9e5f289203db2dfd4db82852a (patch)
tree8522e8eb3e77534ecf5c8e0208746b587eae887b /src
parentAdd Attachment::download{,to_directory} (diff)
downloadserenity-a114a55efb5b08f9e5f289203db2dfd4db82852a.tar.xz
serenity-a114a55efb5b08f9e5f289203db2dfd4db82852a.zip
Add webhook support
Diffstat (limited to 'src')
-rw-r--r--src/builder.rs92
-rw-r--r--src/client/dispatch.rs10
-rw-r--r--src/client/event_store.rs1
-rw-r--r--src/client/http/mod.rs342
-rw-r--r--src/client/http/ratelimiting.rs4
-rw-r--r--src/client/mod.rs10
-rw-r--r--src/model/channel.rs23
-rw-r--r--src/model/gateway.rs16
-rw-r--r--src/model/guild.rs20
-rw-r--r--src/model/id.rs29
-rw-r--r--src/model/mod.rs4
-rw-r--r--src/model/webhook.rs168
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),
+ }
+ }
+}