discord_webhook2/
webhook.rs

1use std::collections::BTreeMap;
2
3use reqwest::multipart::{Form, Part};
4use reqwest::{Client, Url};
5
6use crate::error::DiscordWebhookError;
7use crate::id::DiscordID;
8use crate::message::Message;
9
10pub struct DiscordWebhook {
11    client: Client,
12    url: Url,
13}
14
15#[cfg(not(feature = "rustls-tls"))]
16fn build_client() -> Result<Client, DiscordWebhookError> {
17    Ok(reqwest::Client::new())
18}
19
20#[cfg(feature = "rustls-tls")]
21fn build_client() -> Result<Client, DiscordWebhookError> {
22    reqwest::ClientBuilder::new()
23        .use_rustls_tls()
24        .build()
25        .map_err(DiscordWebhookError::ReqwestError)
26}
27
28impl DiscordWebhook {
29    pub fn new(url: impl Into<String>) -> Result<Self, DiscordWebhookError> {
30        let url = url.into();
31        let url = Url::parse(url.as_str()).map_err(DiscordWebhookError::UrlParseError)?;
32
33        let client = build_client()?;
34
35        Ok(Self { client, url })
36    }
37
38    pub fn from_url(url: Url) -> Self {
39        Self {
40            client: Client::new(),
41            url,
42        }
43    }
44
45    /// Send the given Message object. Returns the ID of the sent message as a result
46    pub async fn send(&self, message: &Message) -> Result<DiscordID, DiscordWebhookError> {
47        let send_result = self
48            .client
49            .post(self.url.join("?wait=true")?.clone())
50            .json(message)
51            .send()
52            .await;
53
54        let response = send_result.map_err(DiscordWebhookError::ReqwestError)?;
55
56        match response.status().is_success() {
57            true => {
58                let posted_message: Message = response
59                    .json::<Message>()
60                    .await
61                    .map_err(DiscordWebhookError::ReqwestError)?;
62
63                match posted_message.id {
64                    None => Err(DiscordWebhookError::FormatError(String::from(
65                        "Missing field `id` in response",
66                    ))),
67                    Some(v) => Ok(v),
68                }
69            }
70            false => Err(DiscordWebhookError::FormatError(
71                response.text().await?.to_string(),
72            )),
73        }
74    }
75
76    /// Sends the given Message object. Returns the ID of the sent message as a result
77    pub async fn send_with_files(
78        &self,
79        message: &Message,
80        files_entries: BTreeMap<String, Vec<u8>>,
81    ) -> Result<DiscordID, DiscordWebhookError> {
82        let mut form = Form::new().text("payload_json", serde_json::to_string(message).unwrap());
83
84        for (i, (name, data)) in files_entries.into_iter().enumerate() {
85            let mut part = Part::bytes(data);
86
87            part = part.file_name(name);
88
89            form = form.part(format!("files[{i}]"), part);
90        }
91
92        let send_result = self
93            .client
94            .post(self.url.join("?wait=true")?.clone())
95            .multipart(form)
96            .send()
97            .await;
98
99        let response = send_result.map_err(DiscordWebhookError::ReqwestError)?;
100
101        match response.status().is_success() {
102            true => {
103                let posted_message: Message = response
104                    .json::<Message>()
105                    .await
106                    .map_err(DiscordWebhookError::ReqwestError)?;
107
108                match posted_message.id {
109                    None => Err(DiscordWebhookError::FormatError(String::from(
110                        "Missing field `id` in response",
111                    ))),
112                    Some(v) => Ok(v),
113                }
114            }
115            false => Err(DiscordWebhookError::FormatError(
116                response.text().await?.to_string(),
117            )),
118        }
119    }
120
121    /// Returns a previously sent webhook message from the same token.
122    pub async fn get(&self, message_id: &DiscordID) -> Result<Message, DiscordWebhookError> {
123        let url = Url::parse(&format!("{}/", self.url.as_str()))?;
124
125        let send_result = self
126            .client
127            .get(
128                url.join(format!("messages/{}?wait=true", message_id.0).as_str())?
129                    .clone(),
130            )
131            .send()
132            .await;
133
134        let response = send_result.map_err(DiscordWebhookError::ReqwestError)?;
135
136        match response.status().is_success() {
137            true => Ok(response
138                .json::<Message>()
139                .await
140                .map_err(DiscordWebhookError::ReqwestError)?),
141            false => Err(DiscordWebhookError::FormatError(
142                response.text().await?.to_string(),
143            )),
144        }
145    }
146
147    /// Edits a previously sent webhook message from the same token.
148    pub async fn edit(
149        &self,
150        message_id: &DiscordID,
151        message: &Message,
152    ) -> Result<DiscordID, DiscordWebhookError> {
153        let url = Url::parse(&format!("{}/", self.url.as_str()))?;
154
155        let send_result = self
156            .client
157            .patch(
158                url.join(format!("messages/{}?wait=true", message_id.0).as_str())?
159                    .clone(),
160            )
161            .json(message)
162            .send()
163            .await;
164
165        let response = send_result.map_err(DiscordWebhookError::ReqwestError)?;
166
167        match response.status().is_success() {
168            true => {
169                let posted_message: Message = response
170                    .json::<Message>()
171                    .await
172                    .map_err(DiscordWebhookError::ReqwestError)?;
173
174                match posted_message.id {
175                    None => Err(DiscordWebhookError::FormatError(String::from(
176                        "Missing field `id` in response",
177                    ))),
178                    Some(v) => Ok(v),
179                }
180            }
181            false => Err(DiscordWebhookError::FormatError(
182                response.text().await?.to_string(),
183            )),
184        }
185    }
186
187    /// Deletes a message created by the webhook.
188    pub async fn delete(&self, message_id: &DiscordID) -> Result<(), DiscordWebhookError> {
189        let url = Url::parse(&format!("{}/", &self.url))?;
190
191        let send_result = self
192            .client
193            .delete(url.join(&format!("messages/{}", message_id.0))?.clone())
194            .send()
195            .await;
196
197        send_result.map_err(DiscordWebhookError::ReqwestError)?;
198
199        Ok(())
200    }
201}