Skip to content

Commit

Permalink
Allow editing and deleting messages from webhooks (#1267)
Browse files Browse the repository at this point in the history
This adds support for editing and deleting messages originating from webhooks. Current `Message::{edit, delete}` methods cannot be used as the endpoints for webhook messages are different, with slightly different data that can be submitted when editing a message.
  • Loading branch information
arqunis committed Apr 3, 2021
1 parent 95c2f78 commit 8d0b307
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 1 deletion.
54 changes: 54 additions & 0 deletions src/builder/edit_webhook_message.rs
@@ -0,0 +1,54 @@
use std::collections::HashMap;

use super::CreateAllowedMentions;
use crate::internal::prelude::*;
use crate::utils;

/// A builder to specify the fields to edit in an existing [`Webhook`]'s message.
///
/// [`Webhook`]: crate::model::webhook::Webhook
#[derive(Clone, Debug, Default)]
pub struct EditWebhookMessage(pub HashMap<&'static str, Value>);

impl EditWebhookMessage {
/// Set the content of the message.
///
/// **Note**: Message contents must be under 2000 unicode code points.
#[inline]
pub fn content<D: ToString>(&mut self, content: D) -> &mut Self {
self.0.insert("content", Value::String(content.to_string()));
self
}

/// Set the embeds associated with the message.
///
/// This should be used in combination with [`Embed::fake`], creating one
/// or more fake embeds to send to the API.
///
/// # Examples
///
/// Refer to [struct-level documentation of `ExecuteWebhook`] for an example
/// on how to use embeds.
///
/// [`Embed::fake`]: crate::model::channel::Embed::fake
/// [struct-level documentation of `ExecuteWebhook`]: crate::builder::ExecuteWebhook#examples
#[inline]
pub fn embeds(&mut self, embeds: Vec<Value>) -> &mut Self {
self.0.insert("embeds", Value::Array(embeds));
self
}

/// Set the allowed mentions for the message.
pub fn allowed_mentions<F>(&mut self, f: F) -> &mut Self
where
F: FnOnce(&mut CreateAllowedMentions) -> &mut CreateAllowedMentions,
{
let mut allowed_mentions = CreateAllowedMentions::default();
f(&mut allowed_mentions);
let map = utils::hashmap_to_json_map(allowed_mentions.0);
let allowed_mentions = Value::Object(map);

self.0.insert("allowed_mentions", allowed_mentions);
self
}
}
2 changes: 2 additions & 0 deletions src/builder/mod.rs
Expand Up @@ -30,6 +30,7 @@ mod edit_member;
mod edit_message;
mod edit_profile;
mod edit_role;
mod edit_webhook_message;
mod execute_webhook;
mod get_messages;

Expand All @@ -46,6 +47,7 @@ pub use self::{
edit_message::EditMessage,
edit_profile::EditProfile,
edit_role::EditRole,
edit_webhook_message::EditWebhookMessage,
execute_webhook::ExecuteWebhook,
get_messages::GetMessages,
};
Expand Down
41 changes: 41 additions & 0 deletions src/http/client.rs
Expand Up @@ -1410,6 +1410,47 @@ impl Http {
response.json::<Message>().await.map(Some).map_err(From::from)
}

/// Edits a webhook's message by Id.
pub async fn edit_webhook_message(
&self,
webhook_id: u64,
token: &str,
message_id: u64,
map: &JsonMap,
) -> Result<Message> {
let body = serde_json::to_vec(map)?;

self.fire(Request {
body: Some(&body),
headers: None,
route: RouteInfo::EditWebhookMessage {
token,
webhook_id,
message_id,
},
})
.await
}

/// Deletes a webhook's messsage by Id.
pub async fn delete_webhook_message(
&self,
webhook_id: u64,
token: &str,
message_id: u64,
) -> Result<()> {
self.wind(204, Request {
body: None,
headers: None,
route: RouteInfo::DeleteWebhookMessage {
token,
webhook_id,
message_id,
},
})
.await
}

/// Gets the active maintenances from Discord's Status API.
///
/// Does not require authentication.
Expand Down
41 changes: 41 additions & 0 deletions src/http/routing.rs
Expand Up @@ -258,6 +258,12 @@ pub enum Route {
VoiceRegions,
/// Route for the `/webhooks/:webhook_id` path.
WebhooksId(u64),
/// Route for the `/webhooks/:webhook_id/:token/messages/:message_id` path.
///
/// The data is the relevant [`WebhookId`].
///
/// [`WebhookId`]: crate::model::id::WebhookId
WebhooksIdMessagesId(u64),
/// Route for the `/webhooks/:application_id` path.
///
/// The data is the relevant [`ApplicationId`].
Expand Down Expand Up @@ -655,6 +661,13 @@ impl Route {
format!(api!("/webhooks/{}/{}?wait={}"), webhook_id, token, wait)
}

pub fn webhook_message<D>(webhook_id: u64, token: D, message_id: u64) -> String
where
D: Display,
{
format!(api!("/webhooks/{}/{}/messages/{}"), webhook_id, token, message_id)
}

#[cfg(feature = "unstable_discord_api")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable_discord_api")))]
pub fn webhook_original_interaction_response<D: Display>(
Expand Down Expand Up @@ -876,6 +889,11 @@ pub enum RouteInfo<'a> {
token: &'a str,
webhook_id: u64,
},
DeleteWebhookMessage {
token: &'a str,
webhook_id: u64,
message_id: u64,
},
EditChannel {
channel_id: u64,
},
Expand Down Expand Up @@ -944,6 +962,11 @@ pub enum RouteInfo<'a> {
token: &'a str,
webhook_id: u64,
},
EditWebhookMessage {
token: &'a str,
webhook_id: u64,
message_id: u64,
},
ExecuteWebhook {
token: &'a str,
wait: bool,
Expand Down Expand Up @@ -1412,6 +1435,15 @@ impl<'a> RouteInfo<'a> {
Route::WebhooksId(webhook_id),
Cow::from(Route::webhook_with_token(webhook_id, token)),
),
RouteInfo::DeleteWebhookMessage {
token,
webhook_id,
message_id,
} => (
LightMethod::Delete,
Route::WebhooksIdMessagesId(webhook_id),
Cow::from(Route::webhook_message(webhook_id, token, message_id)),
),
RouteInfo::EditChannel {
channel_id,
} => (
Expand Down Expand Up @@ -1545,6 +1577,15 @@ impl<'a> RouteInfo<'a> {
Route::WebhooksId(webhook_id),
Cow::from(Route::webhook_with_token(webhook_id, token)),
),
RouteInfo::EditWebhookMessage {
token,
webhook_id,
message_id,
} => (
LightMethod::Patch,
Route::WebhooksIdMessagesId(webhook_id),
Cow::from(Route::webhook_message(webhook_id, token, message_id)),
),
RouteInfo::ExecuteWebhook {
token,
wait,
Expand Down
56 changes: 55 additions & 1 deletion src/model/webhook.rs
Expand Up @@ -11,7 +11,7 @@ use super::{
user::User,
};
#[cfg(feature = "model")]
use crate::builder::ExecuteWebhook;
use crate::builder::{EditWebhookMessage, ExecuteWebhook};
#[cfg(feature = "model")]
use crate::http::Http;
#[cfg(feature = "model")]
Expand Down Expand Up @@ -320,6 +320,60 @@ impl Webhook {
}
}

/// Edits a webhook message with the fields set via the given builder.
///
/// # Errors
///
/// Returns an [`Error::Model`] if the [`token`] is `None`.
///
/// May also return an [`Error::Http`] if the content is malformed, the webhook's token is invalid, or
/// the given message Id does not belong to the current webhook.
///
/// Or may return an [`Error::Json`] if there is an error deserialising Discord's response.
///
/// [`Error::Model`]: crate::error::Error::Model
/// [`token`]: Self::token
/// [`Error::Http`]: crate::error::Error::Http
/// [`Error::Json`]: crate::error::Error::Json
pub async fn edit_message<F>(
&self,
http: impl AsRef<Http>,
message_id: MessageId,
f: F,
) -> Result<Message>
where
F: FnOnce(&mut EditWebhookMessage) -> &mut EditWebhookMessage,
{
let token = self.token.as_ref().ok_or(ModelError::NoTokenSet)?;
let mut edit_webhook_message = EditWebhookMessage::default();
f(&mut edit_webhook_message);

let map = utils::hashmap_to_json_map(edit_webhook_message.0);

http.as_ref().edit_webhook_message(self.id.0, token, message_id.0, &map).await
}

/// Deletes a webhook message.
///
/// # Errors
///
/// Returns an [`Error::Model`] if the [`token`] is `None`.
///
/// May also return an [`Error::Http`] if the webhook's token is invalid or
/// the given message Id does not belong to the current webhook.
///
/// [`Error::Model`]: crate::error::Error::Model
/// [`token`]: Self::token
/// [`Error::Http`]: crate::error::Error::Http
pub async fn delete_message(
&self,
http: impl AsRef<Http>,
message_id: MessageId,
) -> Result<()> {
let token = self.token.as_ref().ok_or(ModelError::NoTokenSet)?;
http.as_ref().delete_webhook_message(self.id.0, token, message_id.0).await
}

/// Retrieves the latest information about the webhook, editing the
/// webhook in-place.
///
Expand Down

0 comments on commit 8d0b307

Please sign in to comment.