use crate::error::HttpError;
use crate::http::client::HttpClient;
use crate::http::routing::Route;
use crate::model::id::{AttachmentId, ChannelId, MessageId};
use crate::model::message::{
Embed, EmbedAuthor, EmbedField, EmbedFooter, EmbedMedia, Message, MessageReference,
};
impl HttpClient {
pub async fn get_messages(&self, channel_id: ChannelId) -> Result<Vec<Message>, HttpError> {
self.list_messages(channel_id, &ListMessagesParams::default())
.await
}
pub async fn list_messages(
&self,
channel_id: ChannelId,
params: &ListMessagesParams,
) -> Result<Vec<Message>, HttpError> {
self.request_no_body(Route::GetMessages {
channel_id,
query: params.to_query(),
})
.await
}
pub async fn search_messages(
&self,
params: &SearchMessagesRequest,
) -> Result<serde_json::Value, HttpError> {
self.request(Route::SearchMessages, Some(params)).await
}
pub async fn get_message(
&self,
channel_id: ChannelId,
message_id: MessageId,
) -> Result<Message, HttpError> {
self.request_no_body(Route::GetMessage {
channel_id,
message_id,
})
.await
}
pub async fn create_message(
&self,
channel_id: ChannelId,
params: &CreateMessage,
) -> Result<Message, HttpError> {
self.request(Route::CreateMessage { channel_id }, Some(params))
.await
}
pub async fn create_message_with_files(
&self,
channel_id: ChannelId,
params: &CreateMessage,
files: Vec<(String, Vec<u8>)>,
) -> Result<Message, HttpError> {
let payload_json = serde_json::to_string(params)?;
let mut form = reqwest::multipart::Form::new().part(
"payload_json",
reqwest::multipart::Part::text(payload_json).mime_str("application/json")?,
);
for (i, (filename, bytes)) in files.into_iter().enumerate() {
let part = reqwest::multipart::Part::bytes(bytes)
.file_name(filename)
.mime_str("application/octet-stream")?;
form = form.part(format!("files[{i}]"), part);
}
self.fire_multipart(Route::CreateMessage { channel_id }, form)
.await
}
pub async fn edit_message(
&self,
channel_id: ChannelId,
message_id: MessageId,
params: &EditMessage,
) -> Result<Message, HttpError> {
self.request(
Route::EditMessage {
channel_id,
message_id,
},
Some(params),
)
.await
}
pub async fn delete_message(
&self,
channel_id: ChannelId,
message_id: MessageId,
) -> Result<(), HttpError> {
self.request_empty(
Route::DeleteMessage {
channel_id,
message_id,
},
None::<&()>,
)
.await
}
pub async fn delete_message_attachment(
&self,
channel_id: ChannelId,
message_id: MessageId,
attachment_id: AttachmentId,
) -> Result<(), HttpError> {
self.request_empty(
Route::DeleteMessageAttachment {
channel_id,
message_id,
attachment_id,
},
None::<&()>,
)
.await
}
pub async fn bulk_delete_messages(
&self,
channel_id: ChannelId,
message_ids: &[MessageId],
) -> Result<(), HttpError> {
#[derive(serde::Serialize)]
struct Body<'a> {
messages: &'a [MessageId],
}
self.request_empty(
Route::BulkDeleteMessages { channel_id },
Some(&Body {
messages: message_ids,
}),
)
.await
}
pub async fn schedule_message(
&self,
channel_id: ChannelId,
body: &impl serde::Serialize,
) -> Result<serde_json::Value, HttpError> {
self.request(Route::ScheduleMessage { channel_id }, Some(body))
.await
}
pub async fn list_scheduled_messages(&self) -> Result<Vec<serde_json::Value>, HttpError> {
self.request_no_body(Route::ListScheduledMessages).await
}
pub async fn get_scheduled_message(
&self,
scheduled_message_id: crate::model::id::Snowflake,
) -> Result<serde_json::Value, HttpError> {
self.request_no_body(Route::GetScheduledMessage {
scheduled_message_id,
})
.await
}
pub async fn update_scheduled_message(
&self,
scheduled_message_id: crate::model::id::Snowflake,
body: &impl serde::Serialize,
) -> Result<serde_json::Value, HttpError> {
self.request(
Route::UpdateScheduledMessage {
scheduled_message_id,
},
Some(body),
)
.await
}
pub async fn cancel_scheduled_message(
&self,
scheduled_message_id: crate::model::id::Snowflake,
) -> Result<(), HttpError> {
self.request_empty(
Route::CancelScheduledMessage {
scheduled_message_id,
},
None::<&()>,
)
.await
}
pub async fn acknowledge_message(
&self,
channel_id: ChannelId,
message_id: MessageId,
mention_count: Option<i32>,
manual: bool,
) -> Result<(), HttpError> {
#[derive(serde::Serialize)]
struct Body {
#[serde(skip_serializing_if = "Option::is_none")]
mention_count: Option<i32>,
manual: bool,
}
self.request_empty(
Route::AcknowledgeMessage {
channel_id,
message_id,
},
Some(&Body {
mention_count,
manual,
}),
)
.await
}
pub async fn acknowledge_pins(&self, channel_id: ChannelId) -> Result<(), HttpError> {
#[derive(serde::Serialize)]
struct Body {}
self.request_empty(Route::AcknowledgePins { channel_id }, Some(&Body {}))
.await
}
pub async fn clear_channel_read_state(&self, channel_id: ChannelId) -> Result<(), HttpError> {
self.request_empty(Route::ClearChannelReadState { channel_id }, None::<&()>)
.await
}
pub async fn ack_bulk_messages(&self, read_states: &[ReadStateAck]) -> Result<(), HttpError> {
#[derive(serde::Serialize)]
struct Body<'a> {
read_states: &'a [ReadStateAck],
}
self.request_empty(Route::AckBulkMessages, Some(&Body { read_states }))
.await
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ReadStateAck {
pub channel_id: ChannelId,
pub message_id: MessageId,
}
#[derive(Debug, Default, Clone)]
pub struct ListMessagesParams {
pub before: Option<MessageId>,
pub after: Option<MessageId>,
pub around: Option<MessageId>,
pub limit: Option<u32>,
}
impl ListMessagesParams {
pub fn before(mut self, id: MessageId) -> Self {
self.before = Some(id);
self
}
pub fn after(mut self, id: MessageId) -> Self {
self.after = Some(id);
self
}
pub fn around(mut self, id: MessageId) -> Self {
self.around = Some(id);
self
}
pub fn limit(mut self, n: u32) -> Self {
self.limit = Some(n);
self
}
pub(crate) fn to_query(&self) -> String {
let mut parts: Vec<String> = Vec::new();
if let Some(v) = self.before {
parts.push(format!("before={v}"));
}
if let Some(v) = self.after {
parts.push(format!("after={v}"));
}
if let Some(v) = self.around {
parts.push(format!("around={v}"));
}
if let Some(v) = self.limit {
parts.push(format!("limit={v}"));
}
if parts.is_empty() {
String::new()
} else {
format!("?{}", parts.join("&"))
}
}
}
#[derive(Debug, Default, serde::Serialize)]
pub struct SearchMessagesRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hits_per_page: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_id: Option<MessageId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_id: Option<MessageId>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub channel_id: Vec<ChannelId>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub exclude_channel_id: Vec<ChannelId>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub exact_phrases: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct Attachment {
pub id: u32,
pub filename: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Default, serde::Serialize)]
pub struct CreateMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tts: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embeds: Option<Vec<Embed>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_reference: Option<MessageReference>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachments: Option<Vec<Attachment>>,
}
impl CreateMessage {
pub fn new() -> Self {
Self::default()
}
pub fn text(content: impl Into<String>) -> Self {
Self {
content: Some(content.into()),
..Default::default()
}
}
pub fn content(mut self, content: impl Into<String>) -> Self {
self.content = Some(content.into());
self
}
pub fn tts(mut self, tts: bool) -> Self {
self.tts = Some(tts);
self
}
pub fn embed(mut self, build: impl FnOnce(EmbedBuilder) -> EmbedBuilder) -> Self {
let embed = build(EmbedBuilder::new()).build();
self.embeds.get_or_insert_with(Vec::new).push(embed);
self
}
pub fn reply(mut self, message_id: MessageId) -> Self {
self.message_reference = Some(MessageReference {
message_id: Some(message_id),
channel_id: None,
});
self
}
pub fn nonce(mut self, nonce: impl Into<String>) -> Self {
self.nonce = Some(nonce.into());
self
}
}
#[derive(Debug, Default, serde::Serialize)]
pub struct EditMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embeds: Option<Vec<Embed>>,
}
impl EditMessage {
pub fn new() -> Self {
Self::default()
}
pub fn content(mut self, content: impl Into<String>) -> Self {
self.content = Some(content.into());
self
}
pub fn embed(mut self, build: impl FnOnce(EmbedBuilder) -> EmbedBuilder) -> Self {
let embed = build(EmbedBuilder::new()).build();
self.embeds.get_or_insert_with(Vec::new).push(embed);
self
}
}
#[derive(Debug, Default)]
pub struct EmbedBuilder {
title: Option<String>,
description: Option<String>,
url: Option<String>,
timestamp: Option<String>,
color: Option<i32>,
footer_text: Option<String>,
footer_icon_url: Option<String>,
image_url: Option<String>,
thumbnail_url: Option<String>,
author_name: Option<String>,
author_url: Option<String>,
author_icon_url: Option<String>,
fields: Vec<EmbedField>,
}
impl EmbedBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
pub fn timestamp(mut self, timestamp: impl Into<String>) -> Self {
self.timestamp = Some(timestamp.into());
self
}
pub fn timestamp_now(mut self) -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let s = secs;
let days = s / 86400;
let time_of_day = s % 86400;
let hours = time_of_day / 3600;
let minutes = (time_of_day % 3600) / 60;
let seconds = time_of_day % 60;
let mut y = 1970i64;
let mut remaining_days = days as i64;
loop {
let days_in_year = if y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) {
366
} else {
365
};
if remaining_days < days_in_year {
break;
}
remaining_days -= days_in_year;
y += 1;
}
let leap = y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
let month_days: [i64; 12] = [
31,
if leap { 29 } else { 28 },
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
];
let mut m = 0usize;
for (i, &md) in month_days.iter().enumerate() {
if remaining_days < md {
m = i;
break;
}
remaining_days -= md;
}
let d = remaining_days + 1;
self.timestamp = Some(format!(
"{y:04}-{:02}-{d:02}T{hours:02}:{minutes:02}:{seconds:02}Z",
m + 1,
));
self
}
pub fn color(mut self, color: i32) -> Self {
self.color = Some(color);
self
}
pub fn footer(mut self, text: impl Into<String>) -> Self {
self.footer_text = Some(text.into());
self
}
pub fn footer_icon(mut self, icon_url: impl Into<String>) -> Self {
self.footer_icon_url = Some(icon_url.into());
self
}
pub fn image(mut self, url: impl Into<String>) -> Self {
self.image_url = Some(url.into());
self
}
pub fn thumbnail(mut self, url: impl Into<String>) -> Self {
self.thumbnail_url = Some(url.into());
self
}
pub fn author(mut self, name: impl Into<String>) -> Self {
self.author_name = Some(name.into());
self
}
pub fn author_url(mut self, url: impl Into<String>) -> Self {
self.author_url = Some(url.into());
self
}
pub fn author_icon(mut self, icon_url: impl Into<String>) -> Self {
self.author_icon_url = Some(icon_url.into());
self
}
pub fn field(
mut self,
name: impl Into<String>,
value: impl Into<String>,
inline: bool,
) -> Self {
self.fields.push(EmbedField {
name: name.into(),
value: value.into(),
inline: Some(inline),
});
self
}
pub fn build(self) -> Embed {
Embed {
kind: Some("rich".to_string()),
title: self.title,
description: self.description,
url: self.url,
timestamp: self.timestamp,
color: self.color,
footer: self.footer_text.map(|text| EmbedFooter {
text,
icon_url: self.footer_icon_url,
}),
image: self.image_url.map(|url| EmbedMedia {
url,
proxy_url: None,
height: None,
width: None,
}),
thumbnail: self.thumbnail_url.map(|url| EmbedMedia {
url,
proxy_url: None,
height: None,
width: None,
}),
video: None,
author: self.author_name.map(|name| EmbedAuthor {
name,
url: self.author_url,
icon_url: self.author_icon_url,
}),
fields: if self.fields.is_empty() {
None
} else {
Some(self.fields)
},
}
}
}