discord_webhook_lib/
lib.rs

1use 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_message("amessage");
112            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}