discord_webhook_lib/
lib.rs1use anyhow::Error;
2use reqwest::{multipart::{Form, Part}, Client};
3use std::fs;
4use std::path::Path;
5
6pub struct DiscordMessage {
7 client: Client,
8 webhook_url: String,
9 image_path: Option<String>,
10 message: Option<String>,
11 extra_fields: Option<Vec<(String, String)>>,
12}
13
14impl DiscordMessage {
15 pub fn builder(webhook_url: impl Into<String>) -> DiscordMessageBuilder {
16 DiscordMessageBuilder {
17 webhook_url: webhook_url.into(),
18 gif_path: None,
19 message: None,
20 extra_fields: None
21 }
22 }
23
24 pub async fn send(&self) -> Result<(), Box<dyn std::error::Error>> {
25
26 let mut part = Part::text("");
27
28 if let Some(image_path) = self.image_path.as_ref() {
29 let file_bytes = fs::read(image_path)?;
30 let filename = Path::new(image_path)
31 .file_name()
32 .map(|os_str| os_str.to_string_lossy().to_string())
33 .ok_or("Invalid file name")?;
34
35 part = Part::bytes(file_bytes)
36 .file_name(filename.clone())
37 .mime_str("image/gif")?;
38 }
39
40 let mut form = Form::new().part("file1", part);
41 if let Some(ref fields) = self.extra_fields {
42 for (key, value) in fields {
43 form = form.text(key.clone(), value.clone());
44 }
45 }
46
47 if let Some(ref message) = self.message {
48 form = form.text("content", message.clone());
49 }
50
51 let res = self.client
52 .post(&self.webhook_url)
53 .multipart(form)
54 .send()
55 .await?;
56
57 if res.status().is_success() {
58 Ok(())
59 } else {
60 Err(Box::from(Error::msg("unable to send")))
61 }
62 }
63}
64
65pub struct DiscordMessageBuilder {
66 webhook_url: String,
67 gif_path: Option<String>,
68 message: Option<String>,
69 extra_fields: Option<Vec<(String, String)>>,
70}
71
72impl DiscordMessageBuilder {
73 pub fn gif_path(&mut self, path: impl Into<String>) {
74 self.gif_path = Some(path.into());
75 }
76
77 pub fn add_message(&mut self, message: impl Into<String>) {
78 self.message = Option::from(message.into());
79 }
80
81 pub fn add_field(&mut self, key: impl Into<String>, value: impl Into<String>) {
82 match self.extra_fields {
83 Some(ref mut fields) => fields.push((key.into(), value.into())),
84 None => self.extra_fields = Some(vec![(key.into(), value.into())]),
85 }
86 }
87
88 pub fn build(self) -> DiscordMessage {
89 DiscordMessage {
90 client: Client::new(),
91 webhook_url: self.webhook_url,
92 image_path: self.gif_path,
93 message: self.message,
94 extra_fields: self.extra_fields,
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use crate::DiscordMessage;
102 use std::env;
103
104 #[test]
105 fn send_test() {
106 let discord_webhook = env::var_os("DISCORD_WEBHOOK_URL");
107 println!("{:?}", discord_webhook);
108 if let Some(dwebhook) = discord_webhook {
109 let mut builder = DiscordMessage::builder(dwebhook.to_str().unwrap());
110 builder.gif_path("./t.jpg");
111 builder.add_field("username", "test");
113 builder.add_field("content", "filename");
114 let dhm = builder.build();
115
116 let rt = tokio::runtime::Runtime::new().expect("Could not create tokio runtime");
117 rt.block_on(dhm.send()).unwrap();
118 }
119 }
120 #[test]
121 fn send_message_only() {
122 let discord_webhook = env::var_os("DISCORD_WEBHOOK_URL");
123 println!("{:?}", discord_webhook);
124 if let Some(dwebhook) = discord_webhook {
125 let mut builder = DiscordMessage::builder(dwebhook.to_str().unwrap());
126 builder.add_field("username", "test");
127 builder.add_field("content", "a message");
128 let dhm = builder.build();
129
130 let rt = tokio::runtime::Runtime::new().expect("Could not create tokio runtime");
131 rt.block_on(dhm.send()).unwrap();
132 }
133 }
134
135}