Merge branch 'main' into guild-routes

This commit is contained in:
bitfl0wer 2023-08-25 01:11:28 +02:00
commit 9cb8b29487
No known key found for this signature in database
GPG Key ID: 0ACD574FCF5226CF
10 changed files with 451 additions and 288 deletions

View File

@ -16,13 +16,20 @@ impl Channel {
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#get-channel>
pub async fn get(user: &mut ChorusUser, channel_id: Snowflake) -> ChorusResult<Channel> {
let url = user.belongs_to.borrow().urls.api.clone();
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!("{}/channels/{}", url, channel_id))
.header("Authorization", user.token()),
limit_type: LimitType::Channel(channel_id),
};
let chorus_request = ChorusRequest::new(
http::Method::GET,
&format!(
"{}/channels/{}",
user.belongs_to.borrow().urls.api.clone(),
channel_id
),
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.deserialize_response::<Channel>(user).await
}
@ -33,18 +40,24 @@ impl Channel {
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#delete-channel>
pub async fn delete(self, user: &mut ChorusUser) -> ChorusResult<()> {
let chorus_request = ChorusRequest {
request: Client::new()
.delete(format!(
"{}/channels/{}",
user.belongs_to.borrow().urls.api,
self.id
))
.header("Authorization", user.token()),
limit_type: LimitType::Channel(self.id),
};
chorus_request.handle_request_as_result(user).await
pub async fn delete(
self,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let url = format!("{}/channels/{}", user.belongs_to.borrow().urls.api, self.id,);
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Channel(self.id),
);
request.handle_request_as_result(user).await
}
/// Modifies a channel with the provided data.
@ -64,22 +77,27 @@ impl Channel {
pub async fn modify(
&self,
modify_data: ChannelModifySchema,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<Channel> {
let channel_id = self.id;
let chorus_request = ChorusRequest {
request: Client::new()
.patch(format!(
let url = format!(
"{}/channels/{}",
user.belongs_to.borrow().urls.api,
channel_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&modify_data).unwrap()),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.deserialize_response::<Channel>(user).await
);
let request = ChorusRequest::new(
http::Method::PATCH,
&url,
Some(to_string(&modify_data).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Channel(channel_id),
);
request.deserialize_response::<Channel>(user).await
}
/// Fetches recent messages from a channel.
@ -96,17 +114,22 @@ impl Channel {
channel_id: Snowflake,
user: &mut ChorusUser,
) -> Result<Vec<Message>, ChorusError> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
let url = format!(
"{}/channels/{}/messages",
user.belongs_to.borrow().urls.api,
channel_id
))
.header("Authorization", user.token())
.query(&range),
limit_type: Default::default(),
};
);
let mut chorus_request = ChorusRequest::new(
http::Method::GET,
&url,
None,
None,
None,
Some(user),
Default::default(),
);
chorus_request.request = chorus_request.request.query(&range);
chorus_request
.deserialize_response::<Vec<Message>>(user)
@ -152,20 +175,24 @@ impl Channel {
recipient_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let request = Client::new()
.delete(format!(
let url = format!(
"{}/channels/{}/recipients/{}",
user.belongs_to.borrow().urls.api,
self.id,
recipient_id
))
.header("Authorization", user.token());
ChorusRequest {
request,
limit_type: LimitType::Channel(self.id),
}
.handle_request_as_result(user)
.await
);
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.id),
);
request.handle_request_as_result(user).await
}
/// Modifies the positions of a set of channel objects for the guild. Requires the `MANAGE_CHANNELS` permission.
@ -178,19 +205,22 @@ impl Channel {
guild_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let request = Client::new()
.patch(format!(
let url = format!(
"{}/guilds/{}/channels",
user.belongs_to.borrow().urls.api,
guild_id
))
.header("Authorization", user.token())
.body(to_string(&schema).unwrap());
ChorusRequest {
request,
limit_type: LimitType::Guild(guild_id),
}
.handle_request_as_result(user)
.await
);
let request = ChorusRequest::new(
http::Method::PATCH,
&url,
Some(to_string(&schema).unwrap()),
None,
None,
Some(user),
LimitType::Guild(guild_id),
);
request.handle_request_as_result(user).await
}
}

View File

@ -138,17 +138,20 @@ impl Message {
channel_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<Vec<Message>> {
let chorus_request = ChorusRequest {
request: Client::new()
.get(format!(
let chorus_request = ChorusRequest::new(
http::Method::GET,
format!(
"{}/channels/{}/pins",
user.belongs_to.borrow().urls.api,
channel_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(channel_id),
};
)
.as_str(),
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request
.deserialize_response::<Vec<Message>>(user)
.await
@ -162,21 +165,25 @@ impl Message {
pub async fn sticky(
channel_id: Snowflake,
message_id: Snowflake,
audit_log_reason: Option<&str>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let chorus_request = ChorusRequest {
request: Client::new()
.put(format!(
let request = ChorusRequest::new(
http::Method::PUT,
format!(
"{}/channels/{}/pins/{}",
user.belongs_to.borrow().urls.api,
channel_id,
message_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.handle_request_as_result(user).await
)
.as_str(),
None,
audit_log_reason,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.handle_request_as_result(user).await
}
/// Unpins a message in a channel. Requires the `MANAGE_MESSAGES` permission. Returns a 204 empty response on success.
@ -185,21 +192,25 @@ impl Message {
pub async fn unsticky(
channel_id: Snowflake,
message_id: Snowflake,
audit_log_reason: Option<&str>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let chorus_request = ChorusRequest {
request: Client::new()
.delete(format!(
let request = ChorusRequest::new(
http::Method::DELETE,
format!(
"{}/channels/{}/pins/{}",
user.belongs_to.borrow().urls.api,
channel_id,
message_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.handle_request_as_result(user).await
)
.as_str(),
None,
audit_log_reason,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.handle_request_as_result(user).await
}
/// Returns a specific message object in the channel.
@ -234,19 +245,21 @@ impl Message {
schema: CreateGreetMessage,
user: &mut ChorusUser,
) -> ChorusResult<Message> {
let chorus_request = ChorusRequest {
request: Client::new()
.post(format!(
let request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/messages/greet",
user.belongs_to.borrow().urls.api,
channel_id,
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap()),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.deserialize_response::<Message>(user).await
)
.as_str(),
Some(to_string(&schema).unwrap()),
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.deserialize_response::<Message>(user).await
}
/// Sets the channel's latest acknowledged message (marks a message as read) for the current user.
@ -265,22 +278,22 @@ impl Message {
schema: MessageAck,
user: &mut ChorusUser,
) -> ChorusResult<Option<String>> {
let chorus_request = ChorusRequest {
request: Client::new()
.post(format!(
let request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/messages/{}/ack",
user.belongs_to.borrow().urls.api,
channel_id,
message_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap()),
limit_type: LimitType::Channel(channel_id),
};
chorus_request
.deserialize_response::<Option<String>>(user)
.await
)
.as_str(),
Some(to_string(&schema).unwrap()),
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.deserialize_response::<Option<String>>(user).await
}
/// Crossposts a message in a News Channel to following channels.
@ -294,19 +307,22 @@ impl Message {
message_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<Message> {
let chorus_request = ChorusRequest {
request: Client::new()
.post(format!(
let request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/messages/{}/crosspost",
user.belongs_to.borrow().urls.api,
channel_id,
message_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.deserialize_response::<Message>(user).await
)
.as_str(),
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.deserialize_response::<Message>(user).await
}
/// Hides a message from the feed of the guild the channel belongs to. Returns a 204 empty response on success.
@ -318,18 +334,21 @@ impl Message {
message_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let chorus_request = ChorusRequest {
request: Client::new()
.delete(format!(
let url = format!(
"{}/channels/{}/messages/{}/hide-guild-feed",
user.belongs_to.borrow().urls.api,
channel_id,
message_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(channel_id),
};
);
let chorus_request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.handle_request_as_result(user).await
}
@ -349,19 +368,21 @@ impl Message {
schema: MessageModifySchema,
user: &mut ChorusUser,
) -> ChorusResult<Message> {
let chorus_request = ChorusRequest {
request: Client::new()
.patch(format!(
let url = format!(
"{}/channels/{}/messages/{}",
user.belongs_to.borrow().urls.api,
channel_id,
message_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap()),
limit_type: LimitType::Channel(channel_id),
};
);
let chorus_request = ChorusRequest::new(
http::Method::PATCH,
&url,
Some(to_string(&schema).unwrap()),
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.deserialize_response::<Message>(user).await
}
@ -370,20 +391,26 @@ impl Message {
pub async fn delete(
channel_id: Snowflake,
message_id: Snowflake,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let chorus_request = ChorusRequest {
request: Client::new()
.delete(format!(
let url = format!(
"{}/channels/{}/messages/{}",
user.belongs_to.borrow().urls.api,
channel_id,
message_id
))
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(channel_id),
};
);
let chorus_request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.handle_request_as_result(user).await
}
@ -399,6 +426,7 @@ impl Message {
pub async fn bulk_delete(
channel_id: Snowflake,
messages: Vec<Snowflake>,
audit_log_reason: Option<String>,
user: &mut ChorusUser,
) -> ChorusResult<()> {
if messages.len() < 2 {
@ -406,19 +434,21 @@ impl Message {
error: "`messages` must contain at least 2 entries.".to_string(),
});
}
let chorus_request = ChorusRequest {
request: Client::new()
.post(format!(
let request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/messages/bulk-delete",
user.belongs_to.borrow().urls.api,
channel_id,
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&messages).unwrap()),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.handle_request_as_result(user).await
)
.as_str(),
Some(to_string(&messages).unwrap()),
audit_log_reason.as_deref(),
None,
Some(user),
LimitType::Channel(channel_id),
);
request.handle_request_as_result(user).await
}
/// Acknowledges the currently pinned messages in a channel. Returns a 204 empty response on success.
@ -429,17 +459,21 @@ impl Message {
channel_id: Snowflake,
user: &mut ChorusUser,
) -> ChorusResult<()> {
let chorus_request = ChorusRequest {
request: Client::new()
.post(format!(
let chorus_request = ChorusRequest::new(
http::Method::POST,
format!(
"{}/channels/{}/pins/ack",
user.belongs_to.borrow().urls.api,
channel_id,
))
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(channel_id),
};
)
.as_str(),
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
chorus_request.handle_request_as_result(user).await
}
}

View File

@ -20,9 +20,10 @@ impl types::Channel {
///
/// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/channel#modify-channel-permissions>
pub async fn edit_permissions(
pub async fn modify_permissions(
user: &mut ChorusUser,
channel_id: Snowflake,
audit_log_reason: Option<String>,
overwrite: PermissionOverwrite,
) -> ChorusResult<()> {
let url = format!(
@ -39,12 +40,16 @@ impl types::Channel {
});
}
};
let chorus_request = ChorusRequest {
request: Client::new()
let mut request = Client::new()
.put(url)
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(body),
.body(body);
if let Some(reason) = audit_log_reason {
request = request.header("X-Audit-Log-Reason", reason);
}
let chorus_request = ChorusRequest {
request,
limit_type: LimitType::Channel(channel_id),
};
chorus_request.handle_request_as_result(user).await
@ -69,12 +74,17 @@ impl types::Channel {
channel_id,
overwrite_id
);
let chorus_request = ChorusRequest {
request: Client::new()
.delete(url)
.header("Authorization", user.token()),
limit_type: LimitType::Channel(channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(channel_id),
);
request.handle_request_as_result(user).await
}
}

View File

@ -1,5 +1,3 @@
use reqwest::Client;
use crate::{
api::LimitType,
errors::ChorusResult,
@ -28,13 +26,18 @@ impl ReactionMeta {
self.channel_id,
self.message_id
);
let chorus_request = ChorusRequest {
request: Client::new()
.delete(url)
.header("Authorization", user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
/// Gets a list of users that reacted with a specific emoji to a message.
@ -52,13 +55,18 @@ impl ReactionMeta {
self.message_id,
emoji
);
let chorus_request = ChorusRequest {
request: Client::new().get(url).header("Authorization", user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request
.deserialize_response::<Vec<PublicUser>>(user)
.await
let request = ChorusRequest::new(
http::Method::GET,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.deserialize_response::<Vec<PublicUser>>(user).await
}
/// Deletes all the reactions for a given emoji on a message.
@ -78,13 +86,18 @@ impl ReactionMeta {
self.message_id,
emoji
);
let chorus_request = ChorusRequest {
request: Client::new()
.delete(url)
.header("Authorization", user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
/// Create a reaction on a message.
@ -107,14 +120,18 @@ impl ReactionMeta {
self.message_id,
emoji
);
let chorus_request = ChorusRequest {
request: Client::new()
.put(url)
.header("Authorization", user.token())
.header("Content-Type", "application/json"),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::PUT,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
/// Deletes a reaction the current user has made to the message.
@ -132,13 +149,18 @@ impl ReactionMeta {
self.message_id,
emoji
);
let chorus_request = ChorusRequest {
request: Client::new()
.delete(url)
.header("Authorization", user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
/// Deletes a user's reaction to a message.
@ -164,12 +186,17 @@ impl ReactionMeta {
emoji,
user_id
);
let chorus_request = ChorusRequest {
request: Client::new()
.delete(url)
.header("Authorization", user.token()),
limit_type: LimitType::Channel(self.channel_id),
};
chorus_request.handle_request_as_result(user).await
let request = ChorusRequest::new(
http::Method::DELETE,
&url,
None,
None,
None,
Some(user),
LimitType::Channel(self.channel_id),
);
request.handle_request_as_result(user).await
}
}

View File

@ -81,9 +81,10 @@ impl Guild {
pub async fn create_channel(
&self,
user: &mut ChorusUser,
audit_log_reason: Option<String>,
schema: ChannelCreateSchema,
) -> ChorusResult<Channel> {
Channel::create(user, self.id, schema).await
Channel::create(user, self.id, audit_log_reason, schema).await
}
/// Returns a list of the guild's channels.
@ -224,10 +225,10 @@ impl Channel {
pub async fn create(
user: &mut ChorusUser,
guild_id: Snowflake,
audit_log_reason: Option<String>,
schema: ChannelCreateSchema,
) -> ChorusResult<Channel> {
let chorus_request = ChorusRequest {
request: Client::new()
let mut request = Client::new()
.post(format!(
"{}/guilds/{}/channels",
user.belongs_to.borrow().urls.api,
@ -235,7 +236,12 @@ impl Channel {
))
.header("Authorization", user.token())
.header("Content-Type", "application/json")
.body(to_string(&schema).unwrap()),
.body(to_string(&schema).unwrap());
if let Some(reason) = audit_log_reason {
request = request.header("X-Audit-Log-Reason", reason);
}
let chorus_request = ChorusRequest {
request,
limit_type: LimitType::Guild(guild_id),
};
chorus_request.deserialize_response::<Channel>(user).await

View File

@ -20,6 +20,54 @@ pub struct ChorusRequest {
}
impl ChorusRequest {
/// Makes a new [`ChorusRequest`].
/// # Arguments
/// * `method` - The HTTP method to use. Must be one of the following:
/// * [`http::Method::GET`]
/// * [`http::Method::POST`]
/// * [`http::Method::PUT`]
/// * [`http::Method::DELETE`]
/// * [`http::Method::PATCH`]
/// * [`http::Method::HEAD`]
#[allow(unused_variables)] // TODO: Add mfa_token to request, once we figure out *how* to do so correctly
pub fn new(
method: http::Method,
url: &str,
body: Option<String>,
audit_log_reason: Option<&str>,
mfa_token: Option<&str>,
chorus_user: Option<&mut ChorusUser>,
limit_type: LimitType,
) -> ChorusRequest {
let request = Client::new();
let mut request = match method {
http::Method::GET => request.get(url),
http::Method::POST => request.post(url),
http::Method::PUT => request.put(url),
http::Method::DELETE => request.delete(url),
http::Method::PATCH => request.patch(url),
http::Method::HEAD => request.head(url),
_ => panic!("Illegal state: Method not supported."),
};
if let Some(user) = chorus_user {
request = request.header("Authorization", user.token());
}
if let Some(body) = body {
// ONCE TOLD ME THE WORLD WAS GONNA ROLL ME
request = request
.body(body)
.header("Content-Type", "application/json");
}
if let Some(reason) = audit_log_reason {
request = request.header("X-Audit-Log-Reason", reason);
}
ChorusRequest {
request,
limit_type,
}
}
/// Sends a [`ChorusRequest`]. Checks if the user is rate limited, and if not, sends the request.
/// If the user is not rate limited and the instance has rate limits enabled, it will update the
/// rate limits.

View File

@ -22,7 +22,7 @@ async fn get_channel() {
async fn delete_channel() {
let mut bundle = common::setup().await;
let channel_guard = bundle.channel.write().unwrap().clone();
let result = Channel::delete(channel_guard, &mut bundle.user).await;
let result = Channel::delete(channel_guard, None, &mut bundle.user).await;
assert!(result.is_ok());
common::teardown(bundle).await
}
@ -51,7 +51,10 @@ async fn modify_channel() {
default_thread_rate_limit_per_user: None,
video_quality_mode: None,
};
let modified_channel = channel.modify(modify_data, &mut bundle.user).await.unwrap();
let modified_channel = channel
.modify(modify_data, None, &mut bundle.user)
.await
.unwrap();
assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string()));
let permission_override = PermissionFlags::from_vec(Vec::from([
@ -66,7 +69,12 @@ async fn modify_channel() {
deny: "0".to_string(),
};
let channel_id: Snowflake = bundle.channel.read().unwrap().id;
Channel::edit_permissions(&mut bundle.user, channel_id, permission_override.clone())
Channel::modify_permissions(
&mut bundle.user,
channel_id,
None,
permission_override.clone(),
)
.await
.unwrap();

View File

@ -93,7 +93,7 @@ pub(crate) async fn setup() -> TestBundle {
};
let mut user = instance.register_account(&reg).await.unwrap();
let guild = Guild::create(&mut user, guild_create_schema).await.unwrap();
let channel = Channel::create(&mut user, guild.id, channel_create_schema)
let channel = Channel::create(&mut user, guild.id, None, channel_create_schema)
.await
.unwrap();

View File

@ -44,7 +44,7 @@ async fn test_self_updating_structs() {
..Default::default()
};
received_channel
.modify(modify_schema, &mut bundle.user)
.modify(modify_schema, None, &mut bundle.user)
.await
.unwrap();
assert_eq!(

View File

@ -115,7 +115,7 @@ async fn test_stickies() {
.unwrap(),
Vec::<Message>::new()
);
Message::sticky(channel.id, message.id, &mut bundle.user)
Message::sticky(channel.id, message.id, None, &mut bundle.user)
.await
.unwrap();
assert_eq!(
@ -127,7 +127,7 @@ async fn test_stickies() {
.id,
message.id
);
Message::unsticky(channel.id, message.id, &mut bundle.user)
Message::unsticky(channel.id, message.id, None, &mut bundle.user)
.await
.unwrap();
assert_eq!(