discord_webhook_rs/
webhook.rs

1use super::Embed;
2
3use reqwest::blocking::{
4    Client, Response,
5    multipart::{Form, Part},
6};
7use serde_json::{Map, Value};
8use std::fs;
9use std::path::Path;
10
11#[derive(Debug)]
12pub enum Error {
13    Request(reqwest::Error),
14    File(std::io::Error),
15    MaxFile,
16    MaxContent,
17    MaxEmbed,
18    MaxField,
19    InvalidColor,
20    InvalidEmbed,
21    MissingNameField,
22    MissingValueField,
23}
24
25pub struct Webhook {
26    url: String,
27    username: Option<String>,
28    avatar_url: Option<String>,
29    content: Option<String>,
30    embeds: Vec<Embed>,
31    files: Vec<String>,
32    client: Client,
33}
34
35impl Webhook {
36    pub fn new(url: impl Into<String>) -> Self {
37        Webhook {
38            url: url.into(),
39            username: None,
40            avatar_url: None,
41            content: None,
42            embeds: Vec::new(),
43            files: Vec::new(),
44            client: Client::new(),
45        }
46    }
47
48    pub fn set_client(mut self, client: Client) -> Self {
49        self.client = client;
50        self
51    }
52
53    pub fn username(mut self, username: impl Into<String>) -> Self {
54        self.username = Some(username.into());
55        self
56    }
57
58    pub fn avatar_url(mut self, avatar_url: impl Into<String>) -> Self {
59        self.avatar_url = Some(avatar_url.into());
60        self
61    }
62
63    pub fn content(mut self, content: impl Into<String>) -> Self {
64        self.content = Some(content.into());
65        self
66    }
67
68    pub fn add_embed(mut self, embed: Embed) -> Self {
69        self.embeds.push(embed);
70        self
71    }
72
73    pub fn add_file(mut self, file: impl Into<String>) -> Self {
74        self.files.push(file.into());
75        self
76    }
77
78    fn verify(&self) -> Result<(), Error> {
79        if let Some(content) = &self.content {
80            if content.len() > 2000 {
81                return Err(Error::MaxContent);
82            }
83        }
84
85        if self.embeds.len() > 10 {
86            return Err(Error::MaxEmbed);
87        }
88
89        Ok(())
90    }
91
92    fn build_form(&self) -> Result<Form, Error> {
93        if self.files.len() > 10 {
94            return Err(Error::MaxFile);
95        }
96
97        let mut form = Form::new();
98
99        for (i, file_path) in self.files.iter().enumerate() {
100            let file_bytes = match fs::read(file_path) {
101                Err(why) => return Err(Error::File(why)),
102                Ok(data) => data,
103            };
104
105            let filename = Path::new(file_path)
106                .file_name()
107                .and_then(|name| name.to_str())
108                .unwrap_or("file");
109
110            let part = Part::bytes(file_bytes.to_vec()).file_name(filename.to_string());
111
112            let part = match part.mime_str("application/octet-stream") {
113                Err(why) => return Err(Error::Request(why)),
114                Ok(data) => data,
115            };
116
117            form = form.part(format!("files[{}]", i), part);
118        }
119
120        Ok(form)
121    }
122
123    fn build_body(&self) -> Result<Value, Error> {
124        self.verify()?;
125
126        let mut obj = Map::new(); // Construct Body
127
128        if let Some(content) = &self.content {
129            obj.insert("content".into(), Value::String(content.clone()));
130        } else {
131            obj.insert("content".into(), Value::String("Hello from Rust!".into()));
132        }
133
134        if let Some(username) = &self.username {
135            obj.insert("username".into(), Value::String(username.clone()));
136        }
137
138        if let Some(avatar_url) = &self.avatar_url {
139            obj.insert("avatar_url".into(), Value::String(avatar_url.clone()));
140        }
141
142        obj.insert("embeds".into(), Value::Array(vec![]));
143        for embed in &self.embeds {
144            if let Value::Array(ref mut embeds) = obj["embeds"] {
145                embeds.push(embed.build()?);
146            }
147        }
148
149        Ok(Value::Object(obj))
150    }
151
152    pub fn send(self) -> Result<Response, Error> {
153        let mut form = self.build_form()?;
154        form = form.text("payload_json", self.build_body()?.to_string());
155
156        let req = self.client.post(&self.url).multipart(form);
157
158        match req.send() {
159            Ok(response) => Ok(response),
160            Err(error) => Err(Error::Request(error)),
161        }
162    }
163}